1 | <?php |
---|
2 | require_once ROOTPATH . '/library/oauth2/lib/OAuth2ServerException.php'; |
---|
3 | require_once ROOTPATH . '/library/oauth2/lib/OAuth2AuthenticateException.php'; |
---|
4 | require_once ROOTPATH . '/library/oauth2/lib/OAuth2RedirectException.php'; |
---|
5 | |
---|
6 | /** |
---|
7 | * @mainpage |
---|
8 | * OAuth 2.0 server in PHP, originally written for |
---|
9 | * <a href="http://www.opendining.net/"> Open Dining</a>. Supports |
---|
10 | * <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-20">IETF draft v20</a>. |
---|
11 | * |
---|
12 | * Source repo has sample servers implementations for |
---|
13 | * <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and |
---|
14 | * <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other |
---|
15 | * storage engines. |
---|
16 | * |
---|
17 | * PHP Data Objects supports a variety of databases, including MySQL, |
---|
18 | * Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample |
---|
19 | * to see how it all works. |
---|
20 | * |
---|
21 | * We're expanding the wiki to include more helpful documentation, but for |
---|
22 | * now, your best bet is to view the oauth.php source - it has lots of |
---|
23 | * comments. |
---|
24 | * |
---|
25 | * @author Tim Ridgely <tim.ridgely@gmail.com> |
---|
26 | * @author Aaron Parecki <aaron@parecki.com> |
---|
27 | * @author Edison Wong <hswong3i@pantarei-design.com> |
---|
28 | * @author David Rochwerger <catch.dave@gmail.com> |
---|
29 | * |
---|
30 | * @see http://code.google.com/p/oauth2-php/ |
---|
31 | * @see https://github.com/quizlet/oauth2-php |
---|
32 | */ |
---|
33 | |
---|
34 | /** |
---|
35 | * OAuth2.0 draft v20 server-side implementation. |
---|
36 | * |
---|
37 | * @todo Add support for Message Authentication Code (MAC) token type. |
---|
38 | * |
---|
39 | * @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>. |
---|
40 | * @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>. |
---|
41 | * @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>. |
---|
42 | * @author Refactored (including separating from raw POST/GET) and updated to draft v20 by David Rochwerger <catch.dave@gmail.com>. |
---|
43 | */ |
---|
44 | class OAuth2 { |
---|
45 | |
---|
46 | /** |
---|
47 | * Array of persistent variables stored. |
---|
48 | */ |
---|
49 | protected $conf = array(); |
---|
50 | |
---|
51 | /** |
---|
52 | * Storage engine for authentication server |
---|
53 | * |
---|
54 | * @var IOAuth2Storage |
---|
55 | */ |
---|
56 | protected $storage; |
---|
57 | |
---|
58 | /** |
---|
59 | * Keep track of the old refresh token. So we can unset |
---|
60 | * the old refresh tokens when a new one is issued. |
---|
61 | * |
---|
62 | * @var string |
---|
63 | */ |
---|
64 | protected $oldRefreshToken; |
---|
65 | |
---|
66 | /** |
---|
67 | * Default values for configuration options. |
---|
68 | * |
---|
69 | * @var int |
---|
70 | * @see OAuth2::setDefaultOptions() |
---|
71 | */ |
---|
72 | const DEFAULT_ACCESS_TOKEN_LIFETIME = 3600; |
---|
73 | const DEFAULT_REFRESH_TOKEN_LIFETIME = 1209600; |
---|
74 | const DEFAULT_AUTH_CODE_LIFETIME = 3600; |
---|
75 | const DEFAULT_WWW_REALM = 'Service'; |
---|
76 | |
---|
77 | /** |
---|
78 | * Configurable options. |
---|
79 | * |
---|
80 | * @var string |
---|
81 | */ |
---|
82 | const CONFIG_ACCESS_LIFETIME = 3600; // The lifetime of access token in seconds. |
---|
83 | const CONFIG_REFRESH_LIFETIME = 3600; // The lifetime of refresh token in seconds. |
---|
84 | const CONFIG_AUTH_LIFETIME = 3600; // The lifetime of auth code in seconds. |
---|
85 | const CONFIG_SUPPORTED_SCOPES = 'supported_scopes'; // Array of scopes you want to support |
---|
86 | const CONFIG_TOKEN_TYPE = 'token_type'; // Token type to respond with. Currently only "Bearer" supported. |
---|
87 | const CONFIG_WWW_REALM = 'realm'; |
---|
88 | const CONFIG_ENFORCE_INPUT_REDIRECT = 'enforce_redirect'; // Set to true to enforce redirect_uri on input for both authorize and token steps. |
---|
89 | const CONFIG_ENFORCE_STATE = 'enforce_state'; // Set to true to enforce state to be passed in authorization (see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12) |
---|
90 | |
---|
91 | |
---|
92 | /** |
---|
93 | * Regex to filter out the client identifier (described in Section 2 of IETF draft). |
---|
94 | * |
---|
95 | * IETF draft does not prescribe a format for these, however I've arbitrarily |
---|
96 | * chosen alphanumeric strings with hyphens and underscores, 3-32 characters |
---|
97 | * long. |
---|
98 | * |
---|
99 | * Feel free to change. |
---|
100 | */ |
---|
101 | const CLIENT_ID_REGEXP = '/^[a-z0-9-_]{3,32}$/i'; |
---|
102 | |
---|
103 | /** |
---|
104 | * @defgroup oauth2_section_5 Accessing a Protected Resource |
---|
105 | * @{ |
---|
106 | * |
---|
107 | * Clients access protected resources by presenting an access token to |
---|
108 | * the resource server. Access tokens act as bearer tokens, where the |
---|
109 | * token string acts as a shared symmetric secret. This requires |
---|
110 | * treating the access token with the same care as other secrets (e.g. |
---|
111 | * end-user passwords). Access tokens SHOULD NOT be sent in the clear |
---|
112 | * over an insecure channel. |
---|
113 | * |
---|
114 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7 |
---|
115 | */ |
---|
116 | |
---|
117 | /** |
---|
118 | * Used to define the name of the OAuth access token parameter |
---|
119 | * (POST & GET). This is for the "bearer" token type. |
---|
120 | * Other token types may use different methods and names. |
---|
121 | * |
---|
122 | * IETF Draft section 2 specifies that it should be called "access_token" |
---|
123 | * |
---|
124 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-06#section-2.2 |
---|
125 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-06#section-2.3 |
---|
126 | */ |
---|
127 | const TOKEN_PARAM_NAME = 'access_token'; |
---|
128 | |
---|
129 | /** |
---|
130 | * When using the bearer token type, there is a specifc Authorization header |
---|
131 | * required: "Bearer" |
---|
132 | * |
---|
133 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-04#section-2.1 |
---|
134 | */ |
---|
135 | const TOKEN_BEARER_HEADER_NAME = 'Bearer'; |
---|
136 | |
---|
137 | /** |
---|
138 | * @} |
---|
139 | */ |
---|
140 | |
---|
141 | /** |
---|
142 | * @defgroup oauth2_section_4 Obtaining Authorization |
---|
143 | * @{ |
---|
144 | * |
---|
145 | * When the client interacts with an end-user, the end-user MUST first |
---|
146 | * grant the client authorization to access its protected resources. |
---|
147 | * Once obtained, the end-user authorization grant is expressed as an |
---|
148 | * authorization code which the client uses to obtain an access token. |
---|
149 | * To obtain an end-user authorization, the client sends the end-user to |
---|
150 | * the end-user authorization endpoint. |
---|
151 | * |
---|
152 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4 |
---|
153 | */ |
---|
154 | |
---|
155 | /** |
---|
156 | * List of possible authentication response types. |
---|
157 | * The "authorization_code" mechanism exclusively supports 'code' |
---|
158 | * and the "implicit" mechanism exclusively supports 'token'. |
---|
159 | * |
---|
160 | * @var string |
---|
161 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.1 |
---|
162 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.1 |
---|
163 | */ |
---|
164 | const RESPONSE_TYPE_AUTH_CODE = 'code'; |
---|
165 | const RESPONSE_TYPE_ACCESS_TOKEN = 'token'; |
---|
166 | |
---|
167 | /** |
---|
168 | * @} |
---|
169 | */ |
---|
170 | |
---|
171 | /** |
---|
172 | * @defgroup oauth2_section_5 Obtaining an Access Token |
---|
173 | * @{ |
---|
174 | * |
---|
175 | * The client obtains an access token by authenticating with the |
---|
176 | * authorization server and presenting its authorization grant (in the form of |
---|
177 | * an authorization code, resource owner credentials, an assertion, or a |
---|
178 | * refresh token). |
---|
179 | * |
---|
180 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4 |
---|
181 | */ |
---|
182 | |
---|
183 | /** |
---|
184 | * Grant types support by draft 20 |
---|
185 | */ |
---|
186 | const GRANT_TYPE_AUTH_CODE = 'authorization_code'; |
---|
187 | const GRANT_TYPE_IMPLICIT = 'token'; |
---|
188 | const GRANT_TYPE_USER_CREDENTIALS = 'password'; |
---|
189 | const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials'; |
---|
190 | const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token'; |
---|
191 | const GRANT_TYPE_EXTENSIONS = 'extensions'; |
---|
192 | |
---|
193 | /** |
---|
194 | * Regex to filter out the grant type. |
---|
195 | * NB: For extensibility, the grant type can be a URI |
---|
196 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.5 |
---|
197 | */ |
---|
198 | const GRANT_TYPE_REGEXP = '#^(authorization_code|token|password|client_credentials|refresh_token|http://.*)$#'; |
---|
199 | |
---|
200 | /** |
---|
201 | * @} |
---|
202 | */ |
---|
203 | |
---|
204 | /** |
---|
205 | * Possible token types as defined by draft 20. |
---|
206 | * |
---|
207 | * TODO: Add support for mac (and maybe other types?) |
---|
208 | * |
---|
209 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7.1 |
---|
210 | */ |
---|
211 | const TOKEN_TYPE_BEARER = 'bearer'; |
---|
212 | const TOKEN_TYPE_MAC = 'mac'; // Currently unsupported |
---|
213 | |
---|
214 | |
---|
215 | /** |
---|
216 | * @defgroup self::HTTP_status HTTP status code |
---|
217 | * @{ |
---|
218 | */ |
---|
219 | |
---|
220 | /** |
---|
221 | * HTTP status codes for successful and error states as specified by draft 20. |
---|
222 | * |
---|
223 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2 |
---|
224 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
225 | */ |
---|
226 | const HTTP_FOUND = '302 Found'; |
---|
227 | const HTTP_BAD_REQUEST = '400 Bad Request'; |
---|
228 | const HTTP_UNAUTHORIZED = '401 Unauthorized'; |
---|
229 | const HTTP_FORBIDDEN = '403 Forbidden'; |
---|
230 | const HTTP_UNAVAILABLE = '503 Service Unavailable'; |
---|
231 | |
---|
232 | /** |
---|
233 | * @} |
---|
234 | */ |
---|
235 | |
---|
236 | /** |
---|
237 | * @defgroup oauth2_error Error handling |
---|
238 | * @{ |
---|
239 | * |
---|
240 | * @todo Extend for i18n. |
---|
241 | * @todo Consider moving all error related functionality into a separate class. |
---|
242 | */ |
---|
243 | |
---|
244 | /** |
---|
245 | * The request is missing a required parameter, includes an unsupported |
---|
246 | * parameter or parameter value, or is otherwise malformed. |
---|
247 | * |
---|
248 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
249 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
250 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
251 | */ |
---|
252 | const ERROR_INVALID_REQUEST = 'invalid_request'; |
---|
253 | |
---|
254 | /** |
---|
255 | * The client identifier provided is invalid. |
---|
256 | * |
---|
257 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
258 | */ |
---|
259 | const ERROR_INVALID_CLIENT = 'invalid_client'; |
---|
260 | |
---|
261 | /** |
---|
262 | * The client is not authorized to use the requested response type. |
---|
263 | * |
---|
264 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
265 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
266 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
267 | */ |
---|
268 | const ERROR_UNAUTHORIZED_CLIENT = 'unauthorized_client'; |
---|
269 | |
---|
270 | /** |
---|
271 | * The redirection URI provided does not match a pre-registered value. |
---|
272 | * |
---|
273 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2.4 |
---|
274 | */ |
---|
275 | const ERROR_REDIRECT_URI_MISMATCH = 'redirect_uri_mismatch'; |
---|
276 | |
---|
277 | /** |
---|
278 | * The end-user or authorization server denied the request. |
---|
279 | * This could be returned, for example, if the resource owner decides to reject |
---|
280 | * access to the client at a later point. |
---|
281 | * |
---|
282 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
283 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
284 | */ |
---|
285 | const ERROR_USER_DENIED = 'access_denied'; |
---|
286 | |
---|
287 | /** |
---|
288 | * The requested response type is not supported by the authorization server. |
---|
289 | * |
---|
290 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
291 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
292 | */ |
---|
293 | const ERROR_UNSUPPORTED_RESPONSE_TYPE = 'unsupported_response_type'; |
---|
294 | |
---|
295 | /** |
---|
296 | * The requested scope is invalid, unknown, or malformed. |
---|
297 | * |
---|
298 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
299 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
300 | */ |
---|
301 | const ERROR_INVALID_SCOPE = 'invalid_scope'; |
---|
302 | |
---|
303 | /** |
---|
304 | * The provided authorization grant is invalid, expired, |
---|
305 | * revoked, does not match the redirection URI used in the |
---|
306 | * authorization request, or was issued to another client. |
---|
307 | * |
---|
308 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
309 | */ |
---|
310 | const ERROR_INVALID_GRANT = 'invalid_grant'; |
---|
311 | |
---|
312 | /** |
---|
313 | * The authorization grant is not supported by the authorization server. |
---|
314 | * |
---|
315 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
316 | */ |
---|
317 | const ERROR_UNSUPPORTED_GRANT_TYPE = 'unsupported_grant_type'; |
---|
318 | |
---|
319 | /** |
---|
320 | * The request requires higher privileges than provided by the access token. |
---|
321 | * The resource server SHOULD respond with the HTTP 403 (Forbidden) status |
---|
322 | * code and MAY include the "scope" attribute with the scope necessary to |
---|
323 | * access the protected resource. |
---|
324 | * |
---|
325 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
326 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
327 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
328 | */ |
---|
329 | const ERROR_INSUFFICIENT_SCOPE = 'invalid_scope'; |
---|
330 | |
---|
331 | /** |
---|
332 | * @} |
---|
333 | */ |
---|
334 | |
---|
335 | /** |
---|
336 | * Creates an OAuth2.0 server-side instance. |
---|
337 | * |
---|
338 | * @param $config - An associative array as below of config options. See CONFIG_* constants. |
---|
339 | */ |
---|
340 | public function __construct(IOAuth2Storage $storage, $config = array()) { |
---|
341 | $this->storage = $storage; |
---|
342 | |
---|
343 | // Configuration options |
---|
344 | $this->setDefaultOptions(); |
---|
345 | foreach ( $config as $name => $value ) { |
---|
346 | $this->setVariable($name, $value); |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | /** |
---|
351 | * Default configuration options are specified here. |
---|
352 | */ |
---|
353 | protected function setDefaultOptions() { |
---|
354 | $this->conf = array( |
---|
355 | self::CONFIG_ACCESS_LIFETIME => self::DEFAULT_ACCESS_TOKEN_LIFETIME, |
---|
356 | self::CONFIG_REFRESH_LIFETIME => self::DEFAULT_REFRESH_TOKEN_LIFETIME, |
---|
357 | self::CONFIG_AUTH_LIFETIME => self::DEFAULT_AUTH_CODE_LIFETIME, |
---|
358 | self::CONFIG_WWW_REALM => self::DEFAULT_WWW_REALM, |
---|
359 | self::CONFIG_TOKEN_TYPE => self::TOKEN_TYPE_BEARER, |
---|
360 | self::CONFIG_ENFORCE_INPUT_REDIRECT => FALSE, |
---|
361 | self::CONFIG_ENFORCE_STATE => FALSE, |
---|
362 | self::CONFIG_SUPPORTED_SCOPES => array() // This is expected to be passed in on construction. Scopes can be an aribitrary string. |
---|
363 | ); |
---|
364 | } |
---|
365 | |
---|
366 | /** |
---|
367 | * Returns a persistent variable. |
---|
368 | * |
---|
369 | * @param $name |
---|
370 | * The name of the variable to return. |
---|
371 | * @param $default |
---|
372 | * The default value to use if this variable has never been set. |
---|
373 | * |
---|
374 | * @return |
---|
375 | * The value of the variable. |
---|
376 | */ |
---|
377 | public function getVariable($name, $default = NULL) { |
---|
378 | $name = strtolower($name); |
---|
379 | |
---|
380 | return isset($this->conf[$name]) ? $this->conf[$name] : $default; |
---|
381 | } |
---|
382 | |
---|
383 | /** |
---|
384 | * Sets a persistent variable. |
---|
385 | * |
---|
386 | * @param $name |
---|
387 | * The name of the variable to set. |
---|
388 | * @param $value |
---|
389 | * The value to set. |
---|
390 | */ |
---|
391 | public function setVariable($name, $value) { |
---|
392 | $name = strtolower($name); |
---|
393 | |
---|
394 | $this->conf[$name] = $value; |
---|
395 | return $this; |
---|
396 | } |
---|
397 | |
---|
398 | // Resource protecting (Section 5). |
---|
399 | |
---|
400 | |
---|
401 | /** |
---|
402 | * Check that a valid access token has been provided. |
---|
403 | * The token is returned (as an associative array) if valid. |
---|
404 | * |
---|
405 | * The scope parameter defines any required scope that the token must have. |
---|
406 | * If a scope param is provided and the token does not have the required |
---|
407 | * scope, we bounce the request. |
---|
408 | * |
---|
409 | * Some implementations may choose to return a subset of the protected |
---|
410 | * resource (i.e. "public" data) if the user has not provided an access |
---|
411 | * token or if the access token is invalid or expired. |
---|
412 | * |
---|
413 | * The IETF spec says that we should send a 401 Unauthorized header and |
---|
414 | * bail immediately so that's what the defaults are set to. You can catch |
---|
415 | * the exception thrown and behave differently if you like (log errors, allow |
---|
416 | * public access for missing tokens, etc) |
---|
417 | * |
---|
418 | * @param $scope |
---|
419 | * A space-separated string of required scope(s), if you want to check |
---|
420 | * for scope. |
---|
421 | * @return array |
---|
422 | * Token |
---|
423 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7 |
---|
424 | * |
---|
425 | * @ingroup oauth2_section_7 |
---|
426 | */ |
---|
427 | public function verifyAccessToken($token_param, $scope = NULL) { |
---|
428 | $tokenType = $this->getVariable(self::CONFIG_TOKEN_TYPE); |
---|
429 | $realm = $this->getVariable(self::CONFIG_WWW_REALM); |
---|
430 | |
---|
431 | if (!$token_param) { // Access token was not provided |
---|
432 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', $scope); |
---|
433 | } |
---|
434 | |
---|
435 | // Get the stored token data (from the implementing subclass) |
---|
436 | $token = $this->storage->getAccessToken($token_param); |
---|
437 | if ($token === NULL) { |
---|
438 | throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'The access token provided is invalid.', $scope); |
---|
439 | } |
---|
440 | |
---|
441 | // Check we have a well formed token |
---|
442 | if (!isset($token["expires"]) || !isset($token["client_id"])) { |
---|
443 | throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'Malformed token (missing "expires" or "client_id")', $scope); |
---|
444 | } |
---|
445 | |
---|
446 | // Check token expiration (expires is a mandatory paramter) |
---|
447 | if (isset($token["expires"]) && time() > $token["expires"]) { |
---|
448 | throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'The access token provided has expired.', $scope); |
---|
449 | } |
---|
450 | |
---|
451 | // Check scope, if provided |
---|
452 | // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error |
---|
453 | if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"]))) { |
---|
454 | throw new OAuth2AuthenticateException(self::HTTP_FORBIDDEN, $tokenType, $realm, self::ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', $scope); |
---|
455 | } |
---|
456 | |
---|
457 | |
---|
458 | |
---|
459 | session_write_close(); //Fecha session anterior |
---|
460 | session_id($token['refresh_token']); //Define o id da session o mesmo do resfresh token |
---|
461 | @session_start(); |
---|
462 | |
---|
463 | /* TODO - Verificar melhor solução para verificação de tratamento para sessions já startadas |
---|
464 | if(!isset($_SESSION)) |
---|
465 | session_start(); |
---|
466 | */ |
---|
467 | return $token; |
---|
468 | } |
---|
469 | |
---|
470 | /** |
---|
471 | * This is a convenience function that can be used to get the token, which can then |
---|
472 | * be passed to verifyAccessToken(). The constraints specified by the draft are |
---|
473 | * attempted to be adheared to in this method. |
---|
474 | * |
---|
475 | * As per the Bearer spec (draft 8, section 2) - there are three ways for a client |
---|
476 | * to specify the bearer token, in order of preference: Authorization Header, |
---|
477 | * POST and GET. |
---|
478 | * |
---|
479 | * NB: Resource servers MUST accept tokens via the Authorization scheme |
---|
480 | * (http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2). |
---|
481 | * |
---|
482 | * @todo Should we enforce TLS/SSL in this function? |
---|
483 | * |
---|
484 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.1 |
---|
485 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.2 |
---|
486 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.3 |
---|
487 | * |
---|
488 | * Old Android version bug (at least with version 2.2) |
---|
489 | * @see http://code.google.com/p/android/issues/detail?id=6684 |
---|
490 | * |
---|
491 | * We don't want to test this functionality as it relies on superglobals and headers: |
---|
492 | * @codeCoverageIgnoreStart |
---|
493 | */ |
---|
494 | public function getBearerToken() { |
---|
495 | |
---|
496 | if (isset($_SERVER['HTTP_AUTHORIZATION'])) { |
---|
497 | $headers = trim($_SERVER["HTTP_AUTHORIZATION"]); |
---|
498 | } elseif (function_exists('apache_request_headers')) { |
---|
499 | $requestHeaders = apache_request_headers(); |
---|
500 | |
---|
501 | // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization) |
---|
502 | $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); |
---|
503 | |
---|
504 | if (isset($requestHeaders['Authorization'])) { |
---|
505 | $headers = trim($requestHeaders['Authorization']); |
---|
506 | } |
---|
507 | } |
---|
508 | |
---|
509 | $tokenType = $this->getVariable(self::CONFIG_TOKEN_TYPE); |
---|
510 | $realm = $this->getVariable(self::CONFIG_WWW_REALM); |
---|
511 | // Check that exactly one method was used |
---|
512 | $methodsUsed = !empty($headers) + isset($_GET[self::TOKEN_PARAM_NAME]) + isset($_POST[self::TOKEN_PARAM_NAME]); |
---|
513 | if ($methodsUsed > 1) { |
---|
514 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'Only one method may be used to authenticate at a time (Auth header, GET or POST).'); |
---|
515 | } elseif ($methodsUsed == 0) { |
---|
516 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The access token was not found.'); |
---|
517 | } |
---|
518 | |
---|
519 | // HEADER: Get the access token from the header |
---|
520 | if (!empty($headers)) { |
---|
521 | if (!preg_match('/' . self::TOKEN_BEARER_HEADER_NAME . '\s(\S+)/', $headers, $matches)) { |
---|
522 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'Malformed auth header'); |
---|
523 | } |
---|
524 | |
---|
525 | return $matches[1]; |
---|
526 | } |
---|
527 | // POST: Get the token from POST data |
---|
528 | if (isset($_POST[self::TOKEN_PARAM_NAME])) { |
---|
529 | if ($_SERVER['REQUEST_METHOD'] != 'POST') { |
---|
530 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'When putting the token in the body, the method must be POST.'); |
---|
531 | } |
---|
532 | |
---|
533 | // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable |
---|
534 | if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] != 'application/x-www-form-urlencoded') { |
---|
535 | throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The content type for POST requests must be "application/x-www-form-urlencoded"'); |
---|
536 | } |
---|
537 | |
---|
538 | return $_POST[self::TOKEN_PARAM_NAME]; |
---|
539 | } |
---|
540 | |
---|
541 | // GET method |
---|
542 | return $_GET[self::TOKEN_PARAM_NAME]; |
---|
543 | } |
---|
544 | |
---|
545 | /** @codeCoverageIgnoreEnd */ |
---|
546 | |
---|
547 | /** |
---|
548 | * Check if everything in required scope is contained in available scope. |
---|
549 | * |
---|
550 | * @param $required_scope |
---|
551 | * Required scope to be check with. |
---|
552 | * |
---|
553 | * @return |
---|
554 | * TRUE if everything in required scope is contained in available scope, |
---|
555 | * and False if it isn't. |
---|
556 | * |
---|
557 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7 |
---|
558 | * |
---|
559 | * @ingroup oauth2_section_7 |
---|
560 | */ |
---|
561 | private function checkScope($required_scope, $available_scope) { |
---|
562 | // The required scope should match or be a subset of the available scope |
---|
563 | if (!is_array($required_scope)) { |
---|
564 | $required_scope = explode(' ', trim($required_scope)); |
---|
565 | } |
---|
566 | |
---|
567 | if (!is_array($available_scope)) { |
---|
568 | $available_scope = explode(' ', trim($available_scope)); |
---|
569 | } |
---|
570 | |
---|
571 | return (count(array_diff($required_scope, $available_scope)) == 0); |
---|
572 | } |
---|
573 | |
---|
574 | // Access token granting (Section 4). |
---|
575 | |
---|
576 | |
---|
577 | /** |
---|
578 | * Grant or deny a requested access token. |
---|
579 | * This would be called from the "/token" endpoint as defined in the spec. |
---|
580 | * Obviously, you can call your endpoint whatever you want. |
---|
581 | * |
---|
582 | * @param $inputData - The draft specifies that the parameters should be |
---|
583 | * retrieved from POST, but you can override to whatever method you like. |
---|
584 | * @throws OAuth2ServerException |
---|
585 | * |
---|
586 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4 |
---|
587 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.6 |
---|
588 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-4.1.3 |
---|
589 | * |
---|
590 | * @ingroup oauth2_section_4 |
---|
591 | */ |
---|
592 | //ORIGINAL URI |
---|
593 | // public function grantAccessToken(array $inputData = NULL, array $authHeaders = NULL) { |
---|
594 | // $filters = array( |
---|
595 | // "grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), |
---|
596 | // "scope" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
597 | // "code" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
598 | // "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), |
---|
599 | // "username" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
600 | // "password" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
601 | // "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
602 | // ); |
---|
603 | // |
---|
604 | // // Input data by default can be either POST or GET |
---|
605 | // if (!isset($inputData)) { |
---|
606 | // $inputData = ($_SERVER['REQUEST_METHOD'] == 'POST') ? $_POST : $_GET; |
---|
607 | // } |
---|
608 | // |
---|
609 | // // Basic authorization header |
---|
610 | // $authHeaders = isset($authHeaders) ? $authHeaders : $this->getAuthorizationHeader(); |
---|
611 | // |
---|
612 | // |
---|
613 | // |
---|
614 | // // Filter input data |
---|
615 | // $input = filter_var_array($inputData, $filters); |
---|
616 | // |
---|
617 | // |
---|
618 | // |
---|
619 | // // Grant Type must be specified. |
---|
620 | // if (!$input["grant_type"]) { |
---|
621 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); |
---|
622 | // } |
---|
623 | // |
---|
624 | // // Authorize the client |
---|
625 | // $client = $this->getClientCredentials($inputData, $authHeaders); |
---|
626 | // |
---|
627 | // if ($this->storage->checkClientCredentials($client[0], $client[1]) === FALSE) { |
---|
628 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client credentials are invalid'); |
---|
629 | // } |
---|
630 | // |
---|
631 | // if (!$this->storage->checkRestrictedGrantType($client[0], $input["grant_type"])) { |
---|
632 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id'); |
---|
633 | // } |
---|
634 | // |
---|
635 | // // Do the granting |
---|
636 | // // |
---|
637 | // switch ($input["grant_type"]) { |
---|
638 | // case self::GRANT_TYPE_AUTH_CODE: |
---|
639 | // if (!($this->storage instanceof IOAuth2GrantCode)) { |
---|
640 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
641 | // } |
---|
642 | // |
---|
643 | // if (!$input["code"]) { |
---|
644 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameter. "code" is required'); |
---|
645 | // } |
---|
646 | // |
---|
647 | // if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) { |
---|
648 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, "The redirect URI parameter is required."); |
---|
649 | // } |
---|
650 | // |
---|
651 | // $stored = $this->storage->getAuthCode($input["code"]); |
---|
652 | // |
---|
653 | // // Check the code exists |
---|
654 | // if ($stored === NULL || $client[0] != $stored["client_id"]) { |
---|
655 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "Refresh token doesn't exist or is invalid for the client"); |
---|
656 | // } |
---|
657 | // |
---|
658 | // // Validate the redirect URI. If a redirect URI has been provided on input, it must be validated |
---|
659 | // if ($input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) { |
---|
660 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, "The redirect URI is missing or do not match"); |
---|
661 | // } |
---|
662 | // |
---|
663 | // if ($stored["expires"] < time()) { |
---|
664 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "The authorization code has expired"); |
---|
665 | // } |
---|
666 | // break; |
---|
667 | // |
---|
668 | // case self::GRANT_TYPE_USER_CREDENTIALS: |
---|
669 | // if (!($this->storage instanceof IOAuth2GrantUser)) { |
---|
670 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
671 | // } |
---|
672 | // |
---|
673 | // if (!$input["username"] || !$input["password"]) { |
---|
674 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); |
---|
675 | // } |
---|
676 | // |
---|
677 | // $stored = $this->storage->checkUserCredentials($client[0], $input["username"], $input["password"]); |
---|
678 | // |
---|
679 | // if ($stored === FALSE) { |
---|
680 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); |
---|
681 | // } |
---|
682 | // break; |
---|
683 | // |
---|
684 | // case self::GRANT_TYPE_CLIENT_CREDENTIALS: |
---|
685 | // if (!($this->storage instanceof IOAuth2GrantClient)) { |
---|
686 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
687 | // } |
---|
688 | // |
---|
689 | // if (empty($client[1])) { |
---|
690 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client_secret is mandatory for the "client_credentials" grant type'); |
---|
691 | // } |
---|
692 | // // NB: We don't need to check for $stored==false, because it was checked above already |
---|
693 | // $stored = $this->storage->checkClientCredentialsGrant($client[0], $client[1]); |
---|
694 | // break; |
---|
695 | // |
---|
696 | // case self::GRANT_TYPE_REFRESH_TOKEN: |
---|
697 | // if (!($this->storage instanceof IOAuth2RefreshTokens)) { |
---|
698 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
699 | // } |
---|
700 | // |
---|
701 | // if (!$input["refresh_token"]) { |
---|
702 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); |
---|
703 | // } |
---|
704 | // |
---|
705 | // $stored = $this->storage->getRefreshToken($input["refresh_token"]); |
---|
706 | // |
---|
707 | // if ($stored === NULL || $client[0] != $stored["client_id"]) { |
---|
708 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Invalid refresh token'); |
---|
709 | // } |
---|
710 | // |
---|
711 | // if ($stored["expires"] < time()) { |
---|
712 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Refresh token has expired'); |
---|
713 | // } |
---|
714 | // |
---|
715 | // // store the refresh token locally so we can delete it when a new refresh token is generated |
---|
716 | // $this->oldRefreshToken = $stored["refresh_token"]; |
---|
717 | // break; |
---|
718 | // |
---|
719 | // case self::GRANT_TYPE_IMPLICIT: |
---|
720 | // /* TODO: NOT YET IMPLEMENTED */ |
---|
721 | // throw new OAuth2ServerException('501 Not Implemented', 'This OAuth2 library is not yet complete. This functionality is not implemented yet.'); |
---|
722 | // if (!($this->storage instanceof IOAuth2GrantImplicit)) { |
---|
723 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
724 | // } |
---|
725 | // |
---|
726 | // break; |
---|
727 | // |
---|
728 | // // Extended grant types: |
---|
729 | // case filter_var($input["grant_type"], FILTER_VALIDATE_URL): |
---|
730 | // if (!($this->storage instanceof IOAuth2GrantExtension)) { |
---|
731 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
732 | // } |
---|
733 | // $uri = filter_var($input["grant_type"], FILTER_VALIDATE_URL); |
---|
734 | // $stored = $this->storage->checkGrantExtension($uri, $inputData, $authHeaders); |
---|
735 | // |
---|
736 | // if ($stored === FALSE) { |
---|
737 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); |
---|
738 | // } |
---|
739 | // break; |
---|
740 | // |
---|
741 | // default : |
---|
742 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); |
---|
743 | // } |
---|
744 | // |
---|
745 | // if (!isset($stored["scope"])) { |
---|
746 | // $stored["scope"] = NULL; |
---|
747 | // } |
---|
748 | // |
---|
749 | // // Check scope, if provided |
---|
750 | // if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) { |
---|
751 | // throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.'); |
---|
752 | // } |
---|
753 | // |
---|
754 | // $user_id = isset($stored['user_id']) ? $stored['user_id'] : null; |
---|
755 | // $token = $this->createAccessToken($client[0], $user_id, $stored['scope']); |
---|
756 | // |
---|
757 | // |
---|
758 | // // Send response |
---|
759 | // $this->sendJsonHeaders(); |
---|
760 | // echo json_encode($token); |
---|
761 | // } |
---|
762 | |
---|
763 | public function grantAccessToken(array $inputData = NULL, array $authHeaders = NULL) { |
---|
764 | $filters = array( |
---|
765 | "grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), |
---|
766 | "scope" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
767 | "code" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
768 | "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), |
---|
769 | "username" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
770 | "password" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
771 | "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
772 | ); |
---|
773 | |
---|
774 | // Input data by default can be either POST or GET |
---|
775 | if (!isset($inputData)) { |
---|
776 | $inputData = ($_SERVER['REQUEST_METHOD'] == 'POST') ? $_POST : $_GET; |
---|
777 | } |
---|
778 | |
---|
779 | // Basic authorization header |
---|
780 | $authHeaders = isset($authHeaders) ? $authHeaders : $this->getAuthorizationHeader(); |
---|
781 | |
---|
782 | |
---|
783 | |
---|
784 | // Filter input data |
---|
785 | $input = filter_var_array($inputData, $filters); |
---|
786 | |
---|
787 | |
---|
788 | |
---|
789 | // Grant Type must be specified. |
---|
790 | if (!$input["grant_type"]) { |
---|
791 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); |
---|
792 | } |
---|
793 | |
---|
794 | // Authorize the client |
---|
795 | $client = $this->getClientCredentials($inputData, $authHeaders); |
---|
796 | |
---|
797 | if ($this->storage->checkClientCredentials($client[0], $client[1]) === FALSE) { |
---|
798 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client credentials are invalid'); |
---|
799 | } |
---|
800 | |
---|
801 | if (!$this->storage->checkRestrictedGrantType($client[0], $input["grant_type"])) { |
---|
802 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id'); |
---|
803 | } |
---|
804 | |
---|
805 | // Do the granting |
---|
806 | // |
---|
807 | switch ($input["grant_type"]) { |
---|
808 | case self::GRANT_TYPE_AUTH_CODE: |
---|
809 | if (!($this->storage instanceof IOAuth2GrantCode)) { |
---|
810 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
811 | } |
---|
812 | |
---|
813 | if (!$input["code"]) { |
---|
814 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameter. "code" is required'); |
---|
815 | } |
---|
816 | |
---|
817 | if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) { |
---|
818 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, "The redirect URI parameter is required."); |
---|
819 | } |
---|
820 | |
---|
821 | $stored = $this->storage->getAuthCode($input["code"]); |
---|
822 | |
---|
823 | // Check the code exists |
---|
824 | if ($stored === NULL || $client[0] != $stored["client_id"]) { |
---|
825 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "Refresh token doesn't exist or is invalid for the client"); |
---|
826 | } |
---|
827 | |
---|
828 | // Validate the redirect URI. If a redirect URI has been provided on input, it must be validated |
---|
829 | if ($input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) { |
---|
830 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, "The redirect URI is missing or do not match"); |
---|
831 | } |
---|
832 | |
---|
833 | if ($stored["expires"] < time()) { |
---|
834 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "The authorization code has expired"); |
---|
835 | } |
---|
836 | break; |
---|
837 | |
---|
838 | case self::GRANT_TYPE_USER_CREDENTIALS: |
---|
839 | if (!($this->storage instanceof IOAuth2GrantUser)) { |
---|
840 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
841 | } |
---|
842 | |
---|
843 | if (!$input["username"] || !$input["password"]) { |
---|
844 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); |
---|
845 | } |
---|
846 | |
---|
847 | $stored = $this->storage->checkUserCredentials($client[0], $input["username"], $input["password"]); |
---|
848 | |
---|
849 | if ($stored === FALSE) { |
---|
850 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); |
---|
851 | } |
---|
852 | break; |
---|
853 | |
---|
854 | case self::GRANT_TYPE_CLIENT_CREDENTIALS: |
---|
855 | if (!($this->storage instanceof IOAuth2GrantClient)) { |
---|
856 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
857 | } |
---|
858 | |
---|
859 | if (empty($client[1])) { |
---|
860 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client_secret is mandatory for the "client_credentials" grant type'); |
---|
861 | } |
---|
862 | // NB: We don't need to check for $stored==false, because it was checked above already |
---|
863 | $stored = $this->storage->checkClientCredentialsGrant($client[0], $client[1]); |
---|
864 | break; |
---|
865 | |
---|
866 | case self::GRANT_TYPE_REFRESH_TOKEN: |
---|
867 | if (!($this->storage instanceof IOAuth2RefreshTokens)) { |
---|
868 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
869 | } |
---|
870 | |
---|
871 | if (!$input["refresh_token"]) { |
---|
872 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); |
---|
873 | } |
---|
874 | |
---|
875 | $stored = $this->storage->getRefreshToken($input["refresh_token"]); |
---|
876 | |
---|
877 | if ($stored === NULL || $client[0] != $stored["client_id"]) { |
---|
878 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Invalid refresh token'); |
---|
879 | } |
---|
880 | |
---|
881 | if ($stored["expires"] < time()) { |
---|
882 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Refresh token has expired'); |
---|
883 | } |
---|
884 | |
---|
885 | // store the refresh token locally so we can delete it when a new refresh token is generated |
---|
886 | $this->oldRefreshToken = $stored["refresh_token"]; |
---|
887 | |
---|
888 | session_write_close(); //Fecha session anterior |
---|
889 | session_id($stored['refresh_token']); //Define o id da session o mesmo do resfresh token |
---|
890 | session_start(); |
---|
891 | |
---|
892 | break; |
---|
893 | |
---|
894 | case self::GRANT_TYPE_IMPLICIT: |
---|
895 | /* TODO: NOT YET IMPLEMENTED */ |
---|
896 | throw new OAuth2ServerException('501 Not Implemented', 'This OAuth2 library is not yet complete. This functionality is not implemented yet.'); |
---|
897 | if (!($this->storage instanceof IOAuth2GrantImplicit)) { |
---|
898 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
899 | } |
---|
900 | |
---|
901 | break; |
---|
902 | |
---|
903 | // Extended grant types: |
---|
904 | case filter_var($input["grant_type"], FILTER_VALIDATE_URL): |
---|
905 | if (!($this->storage instanceof IOAuth2GrantExtension)) { |
---|
906 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); |
---|
907 | } |
---|
908 | $uri = filter_var($input["grant_type"], FILTER_VALIDATE_URL); |
---|
909 | $stored = $this->storage->checkGrantExtension($uri, $inputData, $authHeaders); |
---|
910 | |
---|
911 | if ($stored === FALSE) { |
---|
912 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); |
---|
913 | } |
---|
914 | break; |
---|
915 | |
---|
916 | default : |
---|
917 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); |
---|
918 | } |
---|
919 | |
---|
920 | if (!isset($stored["scope"])) { |
---|
921 | $stored["scope"] = NULL; |
---|
922 | } |
---|
923 | |
---|
924 | // Check scope, if provided |
---|
925 | if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) { |
---|
926 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.'); |
---|
927 | } |
---|
928 | |
---|
929 | $user_id = isset($stored['user_id']) ? $stored['user_id'] : null; |
---|
930 | $token = $this->createAccessToken($client[0], $user_id, $stored['scope']); |
---|
931 | |
---|
932 | //Gerando Session Para o refresh_token |
---|
933 | session_write_close(); //Fecha session anterior |
---|
934 | session_start(); |
---|
935 | session_id($token['refresh_token']); //Define o id da session o mesmo do resfresh token |
---|
936 | session_cache_expire(self::CONFIG_REFRESH_LIFETIME); //Define o tempo da session o mesmo do Refresh token |
---|
937 | |
---|
938 | if(isset($input["username"]) && isset($input["password"])) |
---|
939 | { |
---|
940 | $_SESSION['wallet']['user']['uid'] = $input["username"]; |
---|
941 | $_SESSION['wallet']['user']['password'] = $input["password"]; |
---|
942 | $_SESSION['wallet']['user']['uidNumber'] = $user_id; |
---|
943 | } |
---|
944 | |
---|
945 | |
---|
946 | |
---|
947 | //session_write_close(); //Fecha nova session |
---|
948 | |
---|
949 | // Send response |
---|
950 | $this->sendJsonHeaders(); |
---|
951 | echo json_encode($token); |
---|
952 | } |
---|
953 | |
---|
954 | /** |
---|
955 | * Internal function used to get the client credentials from HTTP basic |
---|
956 | * auth or POST data. |
---|
957 | * |
---|
958 | * According to the spec (draft 20), the client_id can be provided in |
---|
959 | * the Basic Authorization header (recommended) or via GET/POST. |
---|
960 | * |
---|
961 | * @return |
---|
962 | * A list containing the client identifier and password, for example |
---|
963 | * @code |
---|
964 | * return array( |
---|
965 | * CLIENT_ID, |
---|
966 | * CLIENT_SECRET |
---|
967 | * ); |
---|
968 | * @endcode |
---|
969 | * |
---|
970 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-2.4.1 |
---|
971 | * |
---|
972 | * @ingroup oauth2_section_2 |
---|
973 | */ |
---|
974 | protected function getClientCredentials(array $inputData, array $authHeaders) { |
---|
975 | |
---|
976 | // Basic Authentication is used |
---|
977 | if (!empty($authHeaders['PHP_AUTH_USER'])) { |
---|
978 | return array($authHeaders['PHP_AUTH_USER'], $authHeaders['PHP_AUTH_PW']); |
---|
979 | } elseif (empty($inputData['client_id'])) { // No credentials were specified |
---|
980 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'Client id was not found in the headers or body'); |
---|
981 | } else { |
---|
982 | // This method is not recommended, but is supported by specification |
---|
983 | return array($inputData['client_id'], $inputData['client_secret']); |
---|
984 | } |
---|
985 | } |
---|
986 | |
---|
987 | // End-user/client Authorization (Section 2 of IETF Draft). |
---|
988 | |
---|
989 | |
---|
990 | /** |
---|
991 | * Pull the authorization request data out of the HTTP request. |
---|
992 | * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it |
---|
993 | * by setting CONFIG_ENFORCE_INPUT_REDIRECT to true. |
---|
994 | * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that |
---|
995 | * CSRF protection is MANDATORY. You can enforce this by setting the CONFIG_ENFORCE_STATE to true. |
---|
996 | * |
---|
997 | * @param $inputData - The draft specifies that the parameters should be |
---|
998 | * retrieved from GET, but you can override to whatever method you like. |
---|
999 | * @return |
---|
1000 | * The authorization parameters so the authorization server can prompt |
---|
1001 | * the user for approval if valid. |
---|
1002 | * |
---|
1003 | * @throws OAuth2ServerException |
---|
1004 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.1 |
---|
1005 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12 |
---|
1006 | * |
---|
1007 | * @ingroup oauth2_section_3 |
---|
1008 | */ |
---|
1009 | public function getAuthorizeParams(array $inputData = NULL) { |
---|
1010 | $filters = array( |
---|
1011 | "client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), |
---|
1012 | "response_type" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
1013 | "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), |
---|
1014 | "state" => array("flags" => FILTER_REQUIRE_SCALAR), |
---|
1015 | "scope" => array("flags" => FILTER_REQUIRE_SCALAR) |
---|
1016 | ); |
---|
1017 | |
---|
1018 | if (!isset($inputData)) { |
---|
1019 | $inputData = $_GET; |
---|
1020 | } |
---|
1021 | $input = filter_var_array($inputData, $filters); |
---|
1022 | |
---|
1023 | // Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI) |
---|
1024 | if (!$input["client_id"]) { |
---|
1025 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, "No client id supplied"); // We don't have a good URI to use |
---|
1026 | } |
---|
1027 | |
---|
1028 | // Get client details |
---|
1029 | $stored = $this->storage->getClientDetails($input["client_id"]); |
---|
1030 | if ($stored === FALSE) { |
---|
1031 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, "Client id does not exist"); |
---|
1032 | } |
---|
1033 | |
---|
1034 | // Make sure a valid redirect_uri was supplied. If specified, it must match the stored URI. |
---|
1035 | // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2 |
---|
1036 | // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1 |
---|
1037 | // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1 |
---|
1038 | if (!$input["redirect_uri"] && !$stored["redirect_uri"]) { |
---|
1039 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'No redirect URL was supplied or stored.'); |
---|
1040 | } |
---|
1041 | if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) { |
---|
1042 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'The redirect URI is mandatory and was not supplied.'); |
---|
1043 | } |
---|
1044 | // Only need to validate if redirect_uri provided on input and stored. |
---|
1045 | if ($stored["redirect_uri"] && $input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) { |
---|
1046 | throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'The redirect URI provided is missing or does not match'); |
---|
1047 | } |
---|
1048 | |
---|
1049 | // Select the redirect URI |
---|
1050 | $input["redirect_uri"] = isset($input["redirect_uri"]) ? $input["redirect_uri"] : $stored["redirect_uri"]; |
---|
1051 | |
---|
1052 | // type and client_id are required |
---|
1053 | if (!$input["response_type"]) { |
---|
1054 | throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_REQUEST, 'Invalid or missing response type.', $input["state"]); |
---|
1055 | } |
---|
1056 | |
---|
1057 | // Check requested auth response type against interfaces of storage engine |
---|
1058 | $reflect = new ReflectionClass($this->storage); |
---|
1059 | if (!$reflect->hasConstant('RESPONSE_TYPE_' . strtoupper($input['response_type']))) { |
---|
1060 | throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, $input["state"]); |
---|
1061 | } |
---|
1062 | |
---|
1063 | // Validate that the requested scope is supported |
---|
1064 | if ($input["scope"] && !$this->checkScope($input["scope"], $this->getVariable(self::CONFIG_SUPPORTED_SCOPES))) { |
---|
1065 | throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.', $input["state"]); |
---|
1066 | } |
---|
1067 | |
---|
1068 | // Validate state parameter exists (if configured to enforce this) |
---|
1069 | if ($this->getVariable(self::CONFIG_ENFORCE_STATE) && !$input["state"]) { |
---|
1070 | throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_REQUEST, "The state parameter is required."); |
---|
1071 | } |
---|
1072 | |
---|
1073 | // Return retrieved client details together with input |
---|
1074 | return ($input + $stored); |
---|
1075 | } |
---|
1076 | |
---|
1077 | /** |
---|
1078 | * Redirect the user appropriately after approval. |
---|
1079 | * |
---|
1080 | * After the user has approved or denied the access request the |
---|
1081 | * authorization server should call this function to redirect the user |
---|
1082 | * appropriately. |
---|
1083 | * |
---|
1084 | * @param $is_authorized |
---|
1085 | * TRUE or FALSE depending on whether the user authorized the access. |
---|
1086 | * @param $user_id |
---|
1087 | * Identifier of user who authorized the client |
---|
1088 | * @param $params |
---|
1089 | * An associative array as below: |
---|
1090 | * - response_type: The requested response: an access token, an |
---|
1091 | * authorization code, or both. |
---|
1092 | * - client_id: The client identifier as described in Section 2. |
---|
1093 | * - redirect_uri: An absolute URI to which the authorization server |
---|
1094 | * will redirect the user-agent to when the end-user authorization |
---|
1095 | * step is completed. |
---|
1096 | * - scope: (optional) The scope of the access request expressed as a |
---|
1097 | * list of space-delimited strings. |
---|
1098 | * - state: (optional) An opaque value used by the client to maintain |
---|
1099 | * state between the request and callback. |
---|
1100 | * |
---|
1101 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4 |
---|
1102 | * |
---|
1103 | * @ingroup oauth2_section_4 |
---|
1104 | */ |
---|
1105 | public function finishClientAuthorization($is_authorized, $user_id = NULL, $params = array()) { |
---|
1106 | |
---|
1107 | // We repeat this, because we need to re-validate. In theory, this could be POSTed |
---|
1108 | // by a 3rd-party (because we are not internally enforcing NONCEs, etc) |
---|
1109 | $params = $this->getAuthorizeParams($params); |
---|
1110 | |
---|
1111 | $params += array('scope' => NULL, 'state' => NULL); |
---|
1112 | extract($params); |
---|
1113 | |
---|
1114 | if ($state !== NULL) { |
---|
1115 | $result["query"]["state"] = $state; |
---|
1116 | } |
---|
1117 | |
---|
1118 | if ($is_authorized === FALSE) { |
---|
1119 | throw new OAuth2RedirectException($redirect_uri, self::ERROR_USER_DENIED, "The user denied access to your application", $state); |
---|
1120 | } else { |
---|
1121 | if ($response_type == self::RESPONSE_TYPE_AUTH_CODE) { |
---|
1122 | $result["query"]["code"] = $this->createAuthCode($client_id, $user_id, $redirect_uri, $scope); |
---|
1123 | } elseif ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) { |
---|
1124 | $result["fragment"] = $this->createAccessToken($client_id, $user_id, $scope); |
---|
1125 | } |
---|
1126 | } |
---|
1127 | |
---|
1128 | $this->doRedirectUriCallback($redirect_uri, $result); |
---|
1129 | } |
---|
1130 | |
---|
1131 | // Other/utility functions. |
---|
1132 | |
---|
1133 | |
---|
1134 | /** |
---|
1135 | * Redirect the user agent. |
---|
1136 | * |
---|
1137 | * Handle both redirect for success or error response. |
---|
1138 | * |
---|
1139 | * @param $redirect_uri |
---|
1140 | * An absolute URI to which the authorization server will redirect |
---|
1141 | * the user-agent to when the end-user authorization step is completed. |
---|
1142 | * @param $params |
---|
1143 | * Parameters to be pass though buildUri(). |
---|
1144 | * |
---|
1145 | * @ingroup oauth2_section_4 |
---|
1146 | */ |
---|
1147 | private function doRedirectUriCallback($redirect_uri, $params) { |
---|
1148 | header("HTTP/1.1 " . self::HTTP_FOUND); |
---|
1149 | header("Location: " . $this->buildUri($redirect_uri, $params)); |
---|
1150 | exit(); |
---|
1151 | } |
---|
1152 | |
---|
1153 | /** |
---|
1154 | * Build the absolute URI based on supplied URI and parameters. |
---|
1155 | * |
---|
1156 | * @param $uri |
---|
1157 | * An absolute URI. |
---|
1158 | * @param $params |
---|
1159 | * Parameters to be append as GET. |
---|
1160 | * |
---|
1161 | * @return |
---|
1162 | * An absolute URI with supplied parameters. |
---|
1163 | * |
---|
1164 | * @ingroup oauth2_section_4 |
---|
1165 | */ |
---|
1166 | private function buildUri($uri, $params) { |
---|
1167 | $parse_url = parse_url($uri); |
---|
1168 | |
---|
1169 | // Add our params to the parsed uri |
---|
1170 | foreach ( $params as $k => $v ) { |
---|
1171 | if (isset($parse_url[$k])) { |
---|
1172 | $parse_url[$k] .= "&" . http_build_query($v); |
---|
1173 | } else { |
---|
1174 | $parse_url[$k] = http_build_query($v); |
---|
1175 | } |
---|
1176 | } |
---|
1177 | |
---|
1178 | // Put humpty dumpty back together |
---|
1179 | return |
---|
1180 | ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "") |
---|
1181 | . ((isset($parse_url["user"])) ? $parse_url["user"] |
---|
1182 | . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "") |
---|
1183 | . ((isset($parse_url["host"])) ? $parse_url["host"] : "") |
---|
1184 | . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "") |
---|
1185 | . ((isset($parse_url["path"])) ? $parse_url["path"] : "") |
---|
1186 | . ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "") |
---|
1187 | . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "") |
---|
1188 | ; |
---|
1189 | } |
---|
1190 | |
---|
1191 | /** |
---|
1192 | * Handle the creation of access token, also issue refresh token if support. |
---|
1193 | * |
---|
1194 | * This belongs in a separate factory, but to keep it simple, I'm just |
---|
1195 | * keeping it here. |
---|
1196 | * |
---|
1197 | * @param $client_id |
---|
1198 | * Client identifier related to the access token. |
---|
1199 | * @param $scope |
---|
1200 | * (optional) Scopes to be stored in space-separated string. |
---|
1201 | * |
---|
1202 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5 |
---|
1203 | * @ingroup oauth2_section_5 |
---|
1204 | */ |
---|
1205 | // Função ORIGINAL; |
---|
1206 | // protected function createAccessToken($client_id, $user_id, $scope = NULL) { |
---|
1207 | // |
---|
1208 | // $token = array( |
---|
1209 | // "access_token" => $this->genAccessToken(), |
---|
1210 | // "expires_in" => $this->getVariable(self::CONFIG_ACCESS_LIFETIME), |
---|
1211 | // "token_type" => $this->getVariable(self::CONFIG_TOKEN_TYPE), |
---|
1212 | // "scope" => $scope |
---|
1213 | // ); |
---|
1214 | // |
---|
1215 | // $this->storage->setAccessToken($token["access_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_ACCESS_LIFETIME), $scope); |
---|
1216 | // |
---|
1217 | // // Issue a refresh token also, if we support them |
---|
1218 | // if ($this->storage instanceof IOAuth2RefreshTokens) { |
---|
1219 | // $token["refresh_token"] = $this->genAccessToken(); |
---|
1220 | // $this->storage->setRefreshToken($token["refresh_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_REFRESH_LIFETIME), $scope); |
---|
1221 | // |
---|
1222 | // // If we've granted a new refresh token, expire the old one |
---|
1223 | // if ($this->oldRefreshToken) { |
---|
1224 | // $this->storage->unsetRefreshToken($this->oldRefreshToken); |
---|
1225 | // unset($this->oldRefreshToken); |
---|
1226 | // } |
---|
1227 | // } |
---|
1228 | // |
---|
1229 | // return $token; |
---|
1230 | // } |
---|
1231 | protected function createAccessToken($client_id, $user_id, $scope = NULL) { |
---|
1232 | |
---|
1233 | $token = array( |
---|
1234 | "access_token" => $this->genAccessToken(), |
---|
1235 | "expires_in" => $this->getVariable(self::CONFIG_ACCESS_LIFETIME), |
---|
1236 | "token_type" => $this->getVariable(self::CONFIG_TOKEN_TYPE), |
---|
1237 | "scope" => $scope |
---|
1238 | ); |
---|
1239 | $token["refresh_token"] = $this->genAccessToken(); |
---|
1240 | |
---|
1241 | $this->storage->setAccessToken($token["access_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_ACCESS_LIFETIME), $scope , $token['refresh_token']); |
---|
1242 | |
---|
1243 | // Issue a refresh token also, if we support them |
---|
1244 | if ($this->storage instanceof IOAuth2RefreshTokens) { |
---|
1245 | $this->storage->setRefreshToken($token["refresh_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_REFRESH_LIFETIME), $scope); |
---|
1246 | |
---|
1247 | // If we've granted a new refresh token, expire the old one |
---|
1248 | if ($this->oldRefreshToken) { |
---|
1249 | $this->storage->unsetRefreshToken($this->oldRefreshToken); |
---|
1250 | unset($this->oldRefreshToken); |
---|
1251 | } |
---|
1252 | } |
---|
1253 | |
---|
1254 | return $token; |
---|
1255 | } |
---|
1256 | |
---|
1257 | /** |
---|
1258 | * Handle the creation of auth code. |
---|
1259 | * |
---|
1260 | * This belongs in a separate factory, but to keep it simple, I'm just |
---|
1261 | * keeping it here. |
---|
1262 | * |
---|
1263 | * @param $client_id |
---|
1264 | * Client identifier related to the access token. |
---|
1265 | * @param $redirect_uri |
---|
1266 | * An absolute URI to which the authorization server will redirect the |
---|
1267 | * user-agent to when the end-user authorization step is completed. |
---|
1268 | * @param $scope |
---|
1269 | * (optional) Scopes to be stored in space-separated string. |
---|
1270 | * |
---|
1271 | * @ingroup oauth2_section_4 |
---|
1272 | */ |
---|
1273 | private function createAuthCode($client_id, $user_id, $redirect_uri, $scope = NULL) { |
---|
1274 | $code = $this->genAuthCode(); |
---|
1275 | $this->storage->setAuthCode($code, $client_id, $user_id, $redirect_uri, time() + $this->getVariable(self::CONFIG_AUTH_LIFETIME), $scope); |
---|
1276 | return $code; |
---|
1277 | } |
---|
1278 | |
---|
1279 | /** |
---|
1280 | * Generates an unique access token. |
---|
1281 | * |
---|
1282 | * Implementing classes may want to override this function to implement |
---|
1283 | * other access token generation schemes. |
---|
1284 | * |
---|
1285 | * @return |
---|
1286 | * An unique access token. |
---|
1287 | * |
---|
1288 | * @ingroup oauth2_section_4 |
---|
1289 | * @see OAuth2::genAuthCode() |
---|
1290 | */ |
---|
1291 | protected function genAccessToken() { |
---|
1292 | $tokenLen = 40; |
---|
1293 | if (file_exists('/dev/urandom')) { // Get 100 bytes of random data |
---|
1294 | $randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true); |
---|
1295 | } else { |
---|
1296 | $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); |
---|
1297 | } |
---|
1298 | return substr(hash('sha512', $randomData), 0, $tokenLen); |
---|
1299 | } |
---|
1300 | |
---|
1301 | /** |
---|
1302 | * Generates an unique auth code. |
---|
1303 | * |
---|
1304 | * Implementing classes may want to override this function to implement |
---|
1305 | * other auth code generation schemes. |
---|
1306 | * |
---|
1307 | * @return |
---|
1308 | * An unique auth code. |
---|
1309 | * |
---|
1310 | * @ingroup oauth2_section_4 |
---|
1311 | * @see OAuth2::genAccessToken() |
---|
1312 | */ |
---|
1313 | protected function genAuthCode() { |
---|
1314 | return $this->genAccessToken(); // let's reuse the same scheme for token generation |
---|
1315 | } |
---|
1316 | |
---|
1317 | /** |
---|
1318 | * Pull out the Authorization HTTP header and return it. |
---|
1319 | * According to draft 20, standard basic authorization is the only |
---|
1320 | * header variable required (this does not apply to extended grant types). |
---|
1321 | * |
---|
1322 | * Implementing classes may need to override this function if need be. |
---|
1323 | * |
---|
1324 | * @todo We may need to re-implement pulling out apache headers to support extended grant types |
---|
1325 | * |
---|
1326 | * @return |
---|
1327 | * An array of the basic username and password provided. |
---|
1328 | * |
---|
1329 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-2.4.1 |
---|
1330 | * @ingroup oauth2_section_2 |
---|
1331 | */ |
---|
1332 | protected function getAuthorizationHeader() { |
---|
1333 | return array( |
---|
1334 | 'PHP_AUTH_USER' => isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '', |
---|
1335 | 'PHP_AUTH_PW' => isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '' |
---|
1336 | ); |
---|
1337 | } |
---|
1338 | |
---|
1339 | /** |
---|
1340 | * Send out HTTP headers for JSON. |
---|
1341 | * |
---|
1342 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.1 |
---|
1343 | * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2 |
---|
1344 | * |
---|
1345 | * @ingroup oauth2_section_5 |
---|
1346 | */ |
---|
1347 | private function sendJsonHeaders() { |
---|
1348 | if (php_sapi_name() === 'cli' || headers_sent()) { |
---|
1349 | return; |
---|
1350 | } |
---|
1351 | |
---|
1352 | header("Content-Type: application/json"); |
---|
1353 | header("Cache-Control: no-store"); |
---|
1354 | } |
---|
1355 | |
---|
1356 | /** |
---|
1357 | * Internal method for validating redirect URI supplied |
---|
1358 | * @param string $inputUri |
---|
1359 | * @param string $storedUri |
---|
1360 | */ |
---|
1361 | protected function validateRedirectUri($inputUri, $storedUri) { |
---|
1362 | if (!$inputUri || !$storedUri) { |
---|
1363 | return false; // if either one is missing, assume INVALID |
---|
1364 | } |
---|
1365 | return strcasecmp(substr($inputUri, 0, strlen($storedUri)), $storedUri) === 0; |
---|
1366 | } |
---|
1367 | } |
---|