]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarOAuthServer.php
Release 6.4.0
[Github/sugarcrm.git] / include / SugarOAuthServer.php
1 <?php
2 /*********************************************************************************
3  * SugarCRM Community Edition is a customer relationship management program developed by
4  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
5  * 
6  * This program is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  * 
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
16  * details.
17  * 
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  * 
23  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
24  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
25  * 
26  * The interactive user interfaces in modified source and object code versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  * 
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the "Powered by
32  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
33  * technical reasons, the Appropriate Legal Notices must display the words
34  * "Powered by SugarCRM".
35  ********************************************************************************/
36
37
38 require_once 'modules/OAuthTokens/OAuthToken.php';
39 require_once 'modules/OAuthKeys/OAuthKey.php';
40 /**
41  * Sugar OAuth provider implementation
42  * @api
43  */
44 class SugarOAuthServer
45 {
46     /**
47      * OAuth token
48      * @var OAuthToken
49      */
50     protected $token;
51
52     /**
53      * Check if everything is OK
54      * @throws OAuthException
55      */
56     protected function check()
57     {
58         if(!function_exists('mhash') && !function_exists('hash_hmac')) {
59             // define exception class
60             throw new OAuthException("MHash extension required for OAuth support");
61         }
62     }
63
64     /**
65      * Is this functionality enabled?
66      */
67     public static function enabled()
68     {
69         return function_exists('mhash') || function_exists('hash_hmac');
70     }
71
72     /**
73      * Find consumer by key
74      * @param $provider
75      */
76     public function lookupConsumer($provider)
77     {
78         // check $provider->consumer_key
79         // on unknown: Zend_Oauth_Provider::CONSUMER_KEY_UNKNOWN
80         // on bad key: Zend_Oauth_Provider::CONSUMER_KEY_REFUSED
81         $GLOBALS['log']->debug("OAUTH: lookupConsumer, key={$provider->consumer_key}");
82         $consumer = OAuthKey::fetchKey($provider->consumer_key);
83         if(!$consumer) {
84             return Zend_Oauth_Provider::CONSUMER_KEY_UNKNOWN;
85         }
86         $provider->consumer_secret = $consumer->c_secret;
87         $this->consumer = $consumer;
88         return Zend_Oauth_Provider::OK;
89     }
90
91     /**
92      * Check timestamps & nonces
93      * @param OAuthProvider $provider
94      */
95     public function timestampNonceChecker($provider)
96     {
97         // FIXME: add ts/nonce verification
98         if(empty($provider->nonce)) {
99             return Zend_Oauth_Provider::BAD_NONCE;
100         }
101         if(empty($provider->timestamp)) {
102             return Zend_Oauth_Provider::BAD_TIMESTAMP;
103         }
104         return OAuthToken::checkNonce($provider->consumer_key, $provider->nonce, $provider->timestamp);
105     }
106
107     /**
108      * Vefiry incoming token
109      * @param OAuthProvider $provider
110      */
111     public function tokenHandler($provider)
112     {
113         $GLOBALS['log']->debug("OAUTH: tokenHandler, token={$provider->token}, verify={$provider->verifier}");
114
115         $token = OAuthToken::load($provider->token);
116         if(empty($token)) {
117             return Zend_Oauth_Provider::TOKEN_REJECTED;
118         }
119         if($token->consumer != $this->consumer->id) {
120             return Zend_Oauth_Provider::TOKEN_REJECTED;
121         }
122         $GLOBALS['log']->debug("OAUTH: tokenHandler, found token=".var_export($token->id, true));
123         if($token->tstate == OAuthToken::REQUEST) {
124             if(!empty($token->verify) && $provider->verifier == $token->verify) {
125                 $provider->token_secret = $token->secret;
126                 $this->token = $token;
127                 return Zend_Oauth_Provider::OK;
128             } else {
129                 return Zend_Oauth_Provider::TOKEN_USED;
130             }
131         }
132         if($token->tstate == OAuthToken::ACCESS) {
133             $provider->token_secret = $token->secret;
134             $this->token = $token;
135             return Zend_Oauth_Provider::OK;
136         }
137         return Zend_Oauth_Provider::TOKEN_REJECTED;
138     }
139
140     /**
141      * Decode POST/GET via from_html()
142      * @return array decoded data
143      */
144     protected function decodePostGet()
145     {
146         $data = $_GET;
147         $data = array_merge($data, $_POST);
148         foreach($data as $k => $v) {
149             $data[$k] = from_html($v);
150         }
151         return $data;
152     }
153
154     /**
155      * Create OAuth provider
156      *
157      * Checks current request for OAuth valitidy
158      * @param bool $add_rest add REST endpoint as request path
159      */
160     public function __construct($req_path = '')
161     {
162         $GLOBALS['log']->debug("OAUTH: __construct($req_path): ".var_export($_REQUEST, true));
163         $this->check();
164         $this->provider = new Zend_Oauth_Provider();
165         try {
166                     $this->provider->setConsumerHandler(array($this,'lookupConsumer'));
167                     $this->provider->setTimestampNonceHandler(array($this,'timestampNonceChecker'));
168                     $this->provider->setTokenHandler(array($this,'tokenHandler'));
169                 if(!empty($req_path)) {
170                         $this->provider->setRequestTokenPath($req_path);  // No token needed for this end point
171                 }
172                 $this->provider->checkOAuthRequest(null, $this->decodePostGet());
173                 if(mt_rand() % 10 == 0) {
174                     // cleanup 1 in 10 times
175                     OAuthToken::cleanup();
176                 }
177         } catch(Exception $e) {
178             $GLOBALS['log']->debug($this->reportProblem($e));
179             throw $e;
180         }
181     }
182
183     /**
184      * Generate request token string
185      * @return string
186      */
187     public function requestToken()
188     {
189         $GLOBALS['log']->debug("OAUTH: requestToken");
190         $token = OAuthToken::generate();
191         $token->setConsumer($this->consumer);
192         $token->save();
193         return $token->queryString();
194     }
195
196     /**
197      * Generate access token string - must have validated request token
198      * @return string
199      */
200     public function accessToken()
201     {
202         $GLOBALS['log']->debug("OAUTH: accessToken");
203         if(empty($this->token) || $this->token->tstate != OAuthToken::REQUEST) {
204             return null;
205         }
206         $this->token->invalidate();
207         $token = OAuthToken::generate();
208         $token->setState(OAuthToken::ACCESS);
209         $token->setConsumer($this->consumer);
210         // transfer user data from request token
211         $token->copyAuthData($this->token);
212         $token->save();
213         return $token->queryString();
214     }
215
216     /**
217      * Return authorization URL
218      * @return string
219      */
220     public function authUrl()
221     {
222         return urlencode(rtrim($GLOBALS['sugar_config']['site_url'],'/')."/index.php?module=OAuthTokens&action=authorize");
223     }
224
225     /**
226      * Fetch current token if it is authorized
227      * @return OAuthToken|null
228      */
229     public function authorizedToken()
230     {
231         if($this->token->tstate == OAuthToken::ACCESS) {
232             return $this->token;
233         }
234         return null;
235     }
236
237     /**
238      * Fetch authorization data from current token
239      * @return mixed Authorization data or null if none
240      */
241     public function authorization()
242     {
243         if($this->token->tstate == OAuthToken::ACCESS) {
244             return $this->token->authdata;
245         }
246         return null;
247     }
248
249     /**
250      * Report OAuth problem as string
251      */
252     public function reportProblem(Exception $e)
253     {
254         return $this->provider->reportProblem($e);
255     }
256 }
257
258 if(!class_exists('OAuthException')) {
259     // we will use this in case oauth extension is not loaded
260     class OAuthException extends Exception {}
261 }