00001 <?php 00002 00003 /** 00004 * @mainpage 00005 * OAuth 2.0 server in PHP, originally written for 00006 * <a href="http://www.opendining.net/"> Open Dining</a>. Supports 00007 * <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10">IETF draft v10</a>. 00008 * 00009 * Source repo has sample servers implementations for 00010 * <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and 00011 * <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other 00012 * storage engines. 00013 * 00014 * PHP Data Objects supports a variety of databases, including MySQL, 00015 * Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample 00016 * to see how it all works. 00017 * 00018 * We're expanding the wiki to include more helpful documentation, but for 00019 * now, your best bet is to view the oauth.php source - it has lots of 00020 * comments. 00021 * 00022 * @author Tim Ridgely <tim.ridgely@gmail.com> 00023 * @author Aaron Parecki <aaron@parecki.com> 00024 * @author Edison Wong <hswong3i@pantarei-design.com> 00025 * 00026 * @see http://code.google.com/p/oauth2-php/ 00027 */ 00028 00029 00030 /** 00031 * The default duration in seconds of the access token lifetime. 00032 */ 00033 define("OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME", 3600); 00034 00035 /** 00036 * The default duration in seconds of the authorization code lifetime. 00037 */ 00038 define("OAUTH2_DEFAULT_AUTH_CODE_LIFETIME", 30); 00039 00040 /** 00041 * The default duration in seconds of the refresh token lifetime. 00042 */ 00043 define("OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME", 1209600); 00044 00045 00046 /** 00047 * @defgroup oauth2_section_2 Client Credentials 00048 * @{ 00049 * 00050 * When interacting with the authorization server, the client identifies 00051 * itself using a client identifier and authenticates using a set of 00052 * client credentials. This specification provides one mechanism for 00053 * authenticating the client using password credentials. 00054 * 00055 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2 00056 */ 00057 00058 /** 00059 * Regex to filter out the client identifier (described in Section 2 of IETF draft). 00060 * 00061 * IETF draft does not prescribe a format for these, however I've arbitrarily 00062 * chosen alphanumeric strings with hyphens and underscores, 3-32 characters 00063 * long. 00064 * 00065 * Feel free to change. 00066 */ 00067 define("OAUTH2_CLIENT_ID_REGEXP", "/^[a-z0-9-_]{3,32}$/i"); 00068 00069 /** 00070 * @} 00071 */ 00072 00073 00074 /** 00075 * @defgroup oauth2_section_3 Obtaining End-User Authorization 00076 * @{ 00077 * 00078 * When the client interacts with an end-user, the end-user MUST first 00079 * grant the client authorization to access its protected resources. 00080 * Once obtained, the end-user access grant is expressed as an 00081 * authorization code which the client uses to obtain an access token. 00082 * To obtain an end-user authorization, the client sends the end-user to 00083 * the end-user authorization endpoint. 00084 * 00085 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 00086 */ 00087 00088 /** 00089 * Denotes "token" authorization response type. 00090 */ 00091 define("OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN", "token"); 00092 00093 /** 00094 * Denotes "code" authorization response type. 00095 */ 00096 define("OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE", "code"); 00097 00098 /** 00099 * Denotes "code-and-token" authorization response type. 00100 */ 00101 define("OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN", "code-and-token"); 00102 00103 /** 00104 * Regex to filter out the authorization response type. 00105 */ 00106 define("OAUTH2_AUTH_RESPONSE_TYPE_REGEXP", "/^(token|code|code-and-token)$/"); 00107 00108 /** 00109 * @} 00110 */ 00111 00112 00113 /** 00114 * @defgroup oauth2_section_4 Obtaining an Access Token 00115 * @{ 00116 * 00117 * The client obtains an access token by authenticating with the 00118 * authorization server and presenting its access grant (in the form of 00119 * an authorization code, resource owner credentials, an assertion, or a 00120 * refresh token). 00121 * 00122 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4 00123 */ 00124 00125 /** 00126 * Denotes "authorization_code" grant types (for token obtaining). 00127 */ 00128 define("OAUTH2_GRANT_TYPE_AUTH_CODE", "authorization_code"); 00129 00130 /** 00131 * Denotes "password" grant types (for token obtaining). 00132 */ 00133 define("OAUTH2_GRANT_TYPE_USER_CREDENTIALS", "password"); 00134 00135 /** 00136 * Denotes "assertion" grant types (for token obtaining). 00137 */ 00138 define("OAUTH2_GRANT_TYPE_ASSERTION", "assertion"); 00139 00140 /** 00141 * Denotes "refresh_token" grant types (for token obtaining). 00142 */ 00143 define("OAUTH2_GRANT_TYPE_REFRESH_TOKEN", "refresh_token"); 00144 00145 /** 00146 * Denotes "none" grant types (for token obtaining). 00147 */ 00148 define("OAUTH2_GRANT_TYPE_NONE", "none"); 00149 00150 /** 00151 * Regex to filter out the grant type. 00152 */ 00153 define("OAUTH2_GRANT_TYPE_REGEXP", "/^(authorization_code|password|assertion|refresh_token|none)$/"); 00154 00155 /** 00156 * @} 00157 */ 00158 00159 00160 /** 00161 * @defgroup oauth2_section_5 Accessing a Protected Resource 00162 * @{ 00163 * 00164 * Clients access protected resources by presenting an access token to 00165 * the resource server. Access tokens act as bearer tokens, where the 00166 * token string acts as a shared symmetric secret. This requires 00167 * treating the access token with the same care as other secrets (e.g. 00168 * end-user passwords). Access tokens SHOULD NOT be sent in the clear 00169 * over an insecure channel. 00170 * 00171 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 00172 */ 00173 00174 /** 00175 * Used to define the name of the OAuth access token parameter (POST/GET/etc.). 00176 * 00177 * IETF Draft sections 5.1.2 and 5.1.3 specify that it should be called 00178 * "oauth_token" but other implementations use things like "access_token". 00179 * 00180 * I won't be heartbroken if you change it, but it might be better to adhere 00181 * to the spec. 00182 * 00183 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.2 00184 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.3 00185 */ 00186 define("OAUTH2_TOKEN_PARAM_NAME", "oauth_token"); 00187 00188 /** 00189 * @} 00190 */ 00191 00192 00193 /** 00194 * @defgroup oauth2_http_status HTTP status code 00195 * @{ 00196 */ 00197 00198 /** 00199 * "Found" HTTP status code. 00200 * 00201 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 00202 */ 00203 define("OAUTH2_HTTP_FOUND", "302 Found"); 00204 00205 /** 00206 * "Bad Request" HTTP status code. 00207 * 00208 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 00209 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00210 */ 00211 define("OAUTH2_HTTP_BAD_REQUEST", "400 Bad Request"); 00212 00213 /** 00214 * "Unauthorized" HTTP status code. 00215 * 00216 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 00217 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00218 */ 00219 define("OAUTH2_HTTP_UNAUTHORIZED", "401 Unauthorized"); 00220 00221 /** 00222 * "Forbidden" HTTP status code. 00223 * 00224 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00225 */ 00226 define("OAUTH2_HTTP_FORBIDDEN", "403 Forbidden"); 00227 00228 /** 00229 * @} 00230 */ 00231 00232 00233 /** 00234 * @defgroup oauth2_error Error handling 00235 * @{ 00236 * 00237 * @todo Extend for i18n. 00238 */ 00239 00240 /** 00241 * The request is missing a required parameter, includes an unsupported 00242 * parameter or parameter value, or is otherwise malformed. 00243 * 00244 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00245 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00246 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00247 */ 00248 define("OAUTH2_ERROR_INVALID_REQUEST", "invalid_request"); 00249 00250 /** 00251 * The client identifier provided is invalid. 00252 * 00253 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00254 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00255 */ 00256 define("OAUTH2_ERROR_INVALID_CLIENT", "invalid_client"); 00257 00258 /** 00259 * The client is not authorized to use the requested response type. 00260 * 00261 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00262 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00263 */ 00264 define("OAUTH2_ERROR_UNAUTHORIZED_CLIENT", "unauthorized_client"); 00265 00266 /** 00267 * The redirection URI provided does not match a pre-registered value. 00268 * 00269 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00270 */ 00271 define("OAUTH2_ERROR_REDIRECT_URI_MISMATCH", "redirect_uri_mismatch"); 00272 00273 /** 00274 * The end-user or authorization server denied the request. 00275 * 00276 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00277 */ 00278 define("OAUTH2_ERROR_USER_DENIED", "access_denied"); 00279 00280 /** 00281 * The requested response type is not supported by the authorization server. 00282 * 00283 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00284 */ 00285 define("OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE", "unsupported_response_type"); 00286 00287 /** 00288 * The requested scope is invalid, unknown, or malformed. 00289 * 00290 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 00291 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00292 */ 00293 define("OAUTH2_ERROR_INVALID_SCOPE", "invalid_scope"); 00294 00295 /** 00296 * The provided access grant is invalid, expired, or revoked (e.g. invalid 00297 * assertion, expired authorization token, bad end-user password credentials, 00298 * or mismatching authorization code and redirection URI). 00299 * 00300 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00301 */ 00302 define("OAUTH2_ERROR_INVALID_GRANT", "invalid_grant"); 00303 00304 /** 00305 * The access grant included - its type or another attribute - is not 00306 * supported by the authorization server. 00307 * 00308 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 00309 */ 00310 define("OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE", "unsupported_grant_type"); 00311 00312 /** 00313 * The access token provided is invalid. Resource servers SHOULD use this 00314 * error code when receiving an expired token which cannot be refreshed to 00315 * indicate to the client that a new authorization is necessary. The resource 00316 * server MUST respond with the HTTP 401 (Unauthorized) status code. 00317 * 00318 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00319 */ 00320 define("OAUTH2_ERROR_INVALID_TOKEN", "invalid_token"); 00321 00322 /** 00323 * The access token provided has expired. Resource servers SHOULD only use 00324 * this error code when the client is expected to be able to handle the 00325 * response and request a new access token using the refresh token issued 00326 * with the expired access token. The resource server MUST respond with the 00327 * HTTP 401 (Unauthorized) status code. 00328 * 00329 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00330 */ 00331 define("OAUTH2_ERROR_EXPIRED_TOKEN", "expired_token"); 00332 00333 /** 00334 * The request requires higher privileges than provided by the access token. 00335 * The resource server SHOULD respond with the HTTP 403 (Forbidden) status 00336 * code and MAY include the "scope" attribute with the scope necessary to 00337 * access the protected resource. 00338 * 00339 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 00340 */ 00341 define("OAUTH2_ERROR_INSUFFICIENT_SCOPE", "insufficient_scope"); 00342 00343 /** 00344 * @} 00345 */ 00346 00347 /** 00348 * OAuth2.0 draft v10 server-side implementation. 00349 * 00350 * @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>. 00351 * @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>. 00352 * @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>. 00353 */ 00354 abstract class OAuth2 { 00355 00356 /** 00357 * Array of persistent variables stored. 00358 */ 00359 protected $conf = array(); 00360 00361 /** 00362 * Returns a persistent variable. 00363 * 00364 * To avoid problems, always use lower case for persistent variable names. 00365 * 00366 * @param $name 00367 * The name of the variable to return. 00368 * @param $default 00369 * The default value to use if this variable has never been set. 00370 * 00371 * @return 00372 * The value of the variable. 00373 */ 00374 public function getVariable($name, $default = NULL) { 00375 return isset($this->conf[$name]) ? $this->conf[$name] : $default; 00376 } 00377 00378 /** 00379 * Sets a persistent variable. 00380 * 00381 * To avoid problems, always use lower case for persistent variable names. 00382 * 00383 * @param $name 00384 * The name of the variable to set. 00385 * @param $value 00386 * The value to set. 00387 */ 00388 public function setVariable($name, $value) { 00389 $this->conf[$name] = $value; 00390 return $this; 00391 } 00392 00393 // Subclasses must implement the following functions. 00394 00395 /** 00396 * Make sure that the client credentials is valid. 00397 * 00398 * @param $client_id 00399 * Client identifier to be check with. 00400 * @param $client_secret 00401 * (optional) If a secret is required, check that they've given the right one. 00402 * 00403 * @return 00404 * TRUE if client credentials are valid, and MUST return FALSE if invalid. 00405 * 00406 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2.1 00407 * 00408 * @ingroup oauth2_section_2 00409 */ 00410 abstract protected function checkClientCredentials($client_id, $client_secret = NULL); 00411 00412 /** 00413 * Get the registered redirect URI of corresponding client_id. 00414 * 00415 * OAuth says we should store request URIs for each registered client. 00416 * Implement this function to grab the stored URI for a given client id. 00417 * 00418 * @param $client_id 00419 * Client identifier to be check with. 00420 * 00421 * @return 00422 * Registered redirect URI of corresponding client identifier, and MUST 00423 * return FALSE if the given client does not exist or is invalid. 00424 * 00425 * @ingroup oauth2_section_3 00426 */ 00427 abstract protected function getRedirectUri($client_id); 00428 00429 /** 00430 * Look up the supplied oauth_token from storage. 00431 * 00432 * We need to retrieve access token data as we create and verify tokens. 00433 * 00434 * @param $oauth_token 00435 * oauth_token to be check with. 00436 * 00437 * @return 00438 * An associative array as below, and return NULL if the supplied oauth_token 00439 * is invalid: 00440 * - client_id: Stored client identifier. 00441 * - expires: Stored expiration in unix timestamp. 00442 * - scope: (optional) Stored scope values in space-separated string. 00443 * 00444 * @ingroup oauth2_section_5 00445 */ 00446 abstract protected function getAccessToken($oauth_token); 00447 00448 /** 00449 * Store the supplied access token values to storage. 00450 * 00451 * We need to store access token data as we create and verify tokens. 00452 * 00453 * @param $oauth_token 00454 * oauth_token to be stored. 00455 * @param $client_id 00456 * Client identifier to be stored. 00457 * @param $expires 00458 * Expiration to be stored. 00459 * @param $scope 00460 * (optional) Scopes to be stored in space-separated string. 00461 * 00462 * @ingroup oauth2_section_4 00463 */ 00464 abstract protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL); 00465 00466 // Stuff that should get overridden by subclasses. 00467 // 00468 // I don't want to make these abstract, because then subclasses would have 00469 // to implement all of them, which is too much work. 00470 // 00471 // So they're just stubs. Override the ones you need. 00472 00473 /** 00474 * Return supported grant types. 00475 * 00476 * You should override this function with something, or else your OAuth 00477 * provider won't support any grant types! 00478 * 00479 * @return 00480 * A list as below. If you support all grant types, then you'd do: 00481 * @code 00482 * return array( 00483 * OAUTH2_GRANT_TYPE_AUTH_CODE, 00484 * OAUTH2_GRANT_TYPE_USER_CREDENTIALS, 00485 * OAUTH2_GRANT_TYPE_ASSERTION, 00486 * OAUTH2_GRANT_TYPE_REFRESH_TOKEN, 00487 * OAUTH2_GRANT_TYPE_NONE, 00488 * ); 00489 * @endcode 00490 * 00491 * @ingroup oauth2_section_4 00492 */ 00493 protected function getSupportedGrantTypes() { 00494 return array(); 00495 } 00496 00497 /** 00498 * Return supported authorization response types. 00499 * 00500 * You should override this function with your supported response types. 00501 * 00502 * @return 00503 * A list as below. If you support all authorization response types, 00504 * then you'd do: 00505 * @code 00506 * return array( 00507 * OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE, 00508 * OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN, 00509 * OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN, 00510 * ); 00511 * @endcode 00512 * 00513 * @ingroup oauth2_section_3 00514 */ 00515 protected function getSupportedAuthResponseTypes() { 00516 return array( 00517 OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE, 00518 OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN, 00519 OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN 00520 ); 00521 } 00522 00523 /** 00524 * Return supported scopes. 00525 * 00526 * If you want to support scope use, then have this function return a list 00527 * of all acceptable scopes (used to throw the invalid-scope error). 00528 * 00529 * @return 00530 * A list as below, for example: 00531 * @code 00532 * return array( 00533 * 'my-friends', 00534 * 'photos', 00535 * 'whatever-else', 00536 * ); 00537 * @endcode 00538 * 00539 * @ingroup oauth2_section_3 00540 */ 00541 protected function getSupportedScopes() { 00542 return array(); 00543 } 00544 00545 /** 00546 * Check restricted authorization response types of corresponding Client 00547 * identifier. 00548 * 00549 * If you want to restrict clients to certain authorization response types, 00550 * override this function. 00551 * 00552 * @param $client_id 00553 * Client identifier to be check with. 00554 * @param $response_type 00555 * Authorization response type to be check with, would be one of the 00556 * values contained in OAUTH2_AUTH_RESPONSE_TYPE_REGEXP. 00557 * 00558 * @return 00559 * TRUE if the authorization response type is supported by this 00560 * client identifier, and FALSE if it isn't. 00561 * 00562 * @ingroup oauth2_section_3 00563 */ 00564 protected function checkRestrictedAuthResponseType($client_id, $response_type) { 00565 return TRUE; 00566 } 00567 00568 /** 00569 * Check restricted grant types of corresponding client identifier. 00570 * 00571 * If you want to restrict clients to certain grant types, override this 00572 * function. 00573 * 00574 * @param $client_id 00575 * Client identifier to be check with. 00576 * @param $grant_type 00577 * Grant type to be check with, would be one of the values contained in 00578 * OAUTH2_GRANT_TYPE_REGEXP. 00579 * 00580 * @return 00581 * TRUE if the grant type is supported by this client identifier, and 00582 * FALSE if it isn't. 00583 * 00584 * @ingroup oauth2_section_4 00585 */ 00586 protected function checkRestrictedGrantType($client_id, $grant_type) { 00587 return TRUE; 00588 } 00589 00590 // Functions that help grant access tokens for various grant types. 00591 00592 /** 00593 * Fetch authorization code data (probably the most common grant type). 00594 * 00595 * Retrieve the stored data for the given authorization code. 00596 * 00597 * Required for OAUTH2_GRANT_TYPE_AUTH_CODE. 00598 * 00599 * @param $code 00600 * Authorization code to be check with. 00601 * 00602 * @return 00603 * An associative array as below, and NULL if the code is invalid: 00604 * - client_id: Stored client identifier. 00605 * - redirect_uri: Stored redirect URI. 00606 * - expires: Stored expiration in unix timestamp. 00607 * - scope: (optional) Stored scope values in space-separated string. 00608 * 00609 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.1 00610 * 00611 * @ingroup oauth2_section_4 00612 */ 00613 protected function getAuthCode($code) { 00614 return NULL; 00615 } 00616 00617 /** 00618 * Take the provided authorization code values and store them somewhere. 00619 * 00620 * This function should be the storage counterpart to getAuthCode(). 00621 * 00622 * If storage fails for some reason, we're not currently checking for 00623 * any sort of success/failure, so you should bail out of the script 00624 * and provide a descriptive fail message. 00625 * 00626 * Required for OAUTH2_GRANT_TYPE_AUTH_CODE. 00627 * 00628 * @param $code 00629 * Authorization code to be stored. 00630 * @param $client_id 00631 * Client identifier to be stored. 00632 * @param $redirect_uri 00633 * Redirect URI to be stored. 00634 * @param $expires 00635 * Expiration to be stored. 00636 * @param $scope 00637 * (optional) Scopes to be stored in space-separated string. 00638 * 00639 * @ingroup oauth2_section_4 00640 */ 00641 protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) { 00642 } 00643 00644 /** 00645 * Grant access tokens for basic user credentials. 00646 * 00647 * Check the supplied username and password for validity. 00648 * 00649 * You can also use the $client_id param to do any checks required based 00650 * on a client, if you need that. 00651 * 00652 * Required for OAUTH2_GRANT_TYPE_USER_CREDENTIALS. 00653 * 00654 * @param $client_id 00655 * Client identifier to be check with. 00656 * @param $username 00657 * Username to be check with. 00658 * @param $password 00659 * Password to be check with. 00660 * 00661 * @return 00662 * TRUE if the username and password are valid, and FALSE if it isn't. 00663 * Moreover, if the username and password are valid, and you want to 00664 * verify the scope of a user's access, return an associative array 00665 * with the scope values as below. We'll check the scope you provide 00666 * against the requested scope before providing an access token: 00667 * @code 00668 * return array( 00669 * 'scope' => <stored scope values (space-separated string)>, 00670 * ); 00671 * @endcode 00672 * 00673 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2 00674 * 00675 * @ingroup oauth2_section_4 00676 */ 00677 protected function checkUserCredentials($client_id, $username, $password) { 00678 return FALSE; 00679 } 00680 00681 /** 00682 * Grant access tokens for assertions. 00683 * 00684 * Check the supplied assertion for validity. 00685 * 00686 * You can also use the $client_id param to do any checks required based 00687 * on a client, if you need that. 00688 * 00689 * Required for OAUTH2_GRANT_TYPE_ASSERTION. 00690 * 00691 * @param $client_id 00692 * Client identifier to be check with. 00693 * @param $assertion_type 00694 * The format of the assertion as defined by the authorization server. 00695 * @param $assertion 00696 * The assertion. 00697 * 00698 * @return 00699 * TRUE if the assertion is valid, and FALSE if it isn't. Moreover, if 00700 * the assertion is valid, and you want to verify the scope of an access 00701 * request, return an associative array with the scope values as below. 00702 * We'll check the scope you provide against the requested scope before 00703 * providing an access token: 00704 * @code 00705 * return array( 00706 * 'scope' => <stored scope values (space-separated string)>, 00707 * ); 00708 * @endcode 00709 * 00710 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3 00711 * 00712 * @ingroup oauth2_section_4 00713 */ 00714 protected function checkAssertion($client_id, $assertion_type, $assertion) { 00715 return FALSE; 00716 } 00717 00718 /** 00719 * Grant refresh access tokens. 00720 * 00721 * Retrieve the stored data for the given refresh token. 00722 * 00723 * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN. 00724 * 00725 * @param $refresh_token 00726 * Refresh token to be check with. 00727 * 00728 * @return 00729 * An associative array as below, and NULL if the refresh_token is 00730 * invalid: 00731 * - client_id: Stored client identifier. 00732 * - expires: Stored expiration unix timestamp. 00733 * - scope: (optional) Stored scope values in space-separated string. 00734 * 00735 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.4 00736 * 00737 * @ingroup oauth2_section_4 00738 */ 00739 protected function getRefreshToken($refresh_token) { 00740 return NULL; 00741 } 00742 00743 /** 00744 * Take the provided refresh token values and store them somewhere. 00745 * 00746 * This function should be the storage counterpart to getRefreshToken(). 00747 * 00748 * If storage fails for some reason, we're not currently checking for 00749 * any sort of success/failure, so you should bail out of the script 00750 * and provide a descriptive fail message. 00751 * 00752 * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN. 00753 * 00754 * @param $refresh_token 00755 * Refresh token to be stored. 00756 * @param $client_id 00757 * Client identifier to be stored. 00758 * @param $expires 00759 * expires to be stored. 00760 * @param $scope 00761 * (optional) Scopes to be stored in space-separated string. 00762 * 00763 * @ingroup oauth2_section_4 00764 */ 00765 protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) { 00766 return; 00767 } 00768 00769 /** 00770 * Expire a used refresh token. 00771 * 00772 * This is not explicitly required in the spec, but is almost implied. 00773 * After granting a new refresh token, the old one is no longer useful and 00774 * so should be forcibly expired in the data store so it can't be used again. 00775 * 00776 * If storage fails for some reason, we're not currently checking for 00777 * any sort of success/failure, so you should bail out of the script 00778 * and provide a descriptive fail message. 00779 * 00780 * @param $refresh_token 00781 * Refresh token to be expirse. 00782 * 00783 * @ingroup oauth2_section_4 00784 */ 00785 protected function unsetRefreshToken($refresh_token) { 00786 return; 00787 } 00788 00789 /** 00790 * Grant access tokens for the "none" grant type. 00791 * 00792 * Not really described in the IETF Draft, so I just left a method 00793 * stub... Do whatever you want! 00794 * 00795 * Required for OAUTH2_GRANT_TYPE_NONE. 00796 * 00797 * @ingroup oauth2_section_4 00798 */ 00799 protected function checkNoneAccess($client_id) { 00800 return FALSE; 00801 } 00802 00803 /** 00804 * Get default authentication realm for WWW-Authenticate header. 00805 * 00806 * Change this to whatever authentication realm you want to send in a 00807 * WWW-Authenticate header. 00808 * 00809 * @return 00810 * A string that you want to send in a WWW-Authenticate header. 00811 * 00812 * @ingroup oauth2_error 00813 */ 00814 protected function getDefaultAuthenticationRealm() { 00815 return "Service"; 00816 } 00817 00818 // End stuff that should get overridden. 00819 00820 /** 00821 * Creates an OAuth2.0 server-side instance. 00822 * 00823 * @param $config 00824 * An associative array as below: 00825 * - access_token_lifetime: (optional) The lifetime of access token in 00826 * seconds. 00827 * - auth_code_lifetime: (optional) The lifetime of authorization code in 00828 * seconds. 00829 * - refresh_token_lifetime: (optional) The lifetime of refresh token in 00830 * seconds. 00831 * - display_error: (optional) Whether to show verbose error messages in 00832 * the response. 00833 */ 00834 public function __construct($config = array()) { 00835 foreach ($config as $name => $value) { 00836 $this->setVariable($name, $value); 00837 } 00838 } 00839 00840 // Resource protecting (Section 5). 00841 00842 /** 00843 * Check that a valid access token has been provided. 00844 * 00845 * The scope parameter defines any required scope that the token must have. 00846 * If a scope param is provided and the token does not have the required 00847 * scope, we bounce the request. 00848 * 00849 * Some implementations may choose to return a subset of the protected 00850 * resource (i.e. "public" data) if the user has not provided an access 00851 * token or if the access token is invalid or expired. 00852 * 00853 * The IETF spec says that we should send a 401 Unauthorized header and 00854 * bail immediately so that's what the defaults are set to. 00855 * 00856 * @param $scope 00857 * A space-separated string of required scope(s), if you want to check 00858 * for scope. 00859 * @param $exit_not_present 00860 * If TRUE and no access token is provided, send a 401 header and exit, 00861 * otherwise return FALSE. 00862 * @param $exit_invalid 00863 * If TRUE and the implementation of getAccessToken() returns NULL, exit, 00864 * otherwise return FALSE. 00865 * @param $exit_expired 00866 * If TRUE and the access token has expired, exit, otherwise return FALSE. 00867 * @param $exit_scope 00868 * If TRUE the access token does not have the required scope(s), exit, 00869 * otherwise return FALSE. 00870 * @param $realm 00871 * If you want to specify a particular realm for the WWW-Authenticate 00872 * header, supply it here. 00873 * 00874 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 00875 * 00876 * @ingroup oauth2_section_5 00877 */ 00878 public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) { 00879 $token_param = $this->getAccessTokenParams(); 00880 if ($token_param === FALSE) // Access token was not provided 00881 return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_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.', NULL, $scope) : FALSE; 00882 // Get the stored token data (from the implementing subclass) 00883 $token = $this->getAccessToken($token_param); 00884 if ($token === NULL) 00885 return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE; 00886 00887 // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages) 00888 if (isset($token["expires"]) && time() > $token["expires"]) 00889 return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE; 00890 00891 // Check scope, if provided 00892 // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error 00893 if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"]))) 00894 return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE; 00895 00896 return TRUE; 00897 } 00898 00899 /** 00900 * Check if everything in required scope is contained in available scope. 00901 * 00902 * @param $required_scope 00903 * Required scope to be check with. 00904 * @param $available_scope 00905 * Available scope to be compare with. 00906 * 00907 * @return 00908 * TRUE if everything in required scope is contained in available scope, 00909 * and False if it isn't. 00910 * 00911 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 00912 * 00913 * @ingroup oauth2_section_5 00914 */ 00915 private function checkScope($required_scope, $available_scope) { 00916 // The required scope should match or be a subset of the available scope 00917 if (!is_array($required_scope)) 00918 $required_scope = explode(" ", $required_scope); 00919 00920 if (!is_array($available_scope)) 00921 $available_scope = explode(" ", $available_scope); 00922 00923 return (count(array_diff($required_scope, $available_scope)) == 0); 00924 } 00925 00926 /** 00927 * Pulls the access token out of the HTTP request. 00928 * 00929 * Either from the Authorization header or GET/POST/etc. 00930 * 00931 * @return 00932 * Access token value if present, and FALSE if it isn't. 00933 * 00934 * @todo Support PUT or DELETE. 00935 * 00936 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1 00937 * 00938 * @ingroup oauth2_section_5 00939 */ 00940 private function getAccessTokenParams() { 00941 $auth_header = $this->getAuthorizationHeader(); 00942 00943 if ($auth_header !== FALSE) { 00944 // Make sure only the auth header is set 00945 if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME]) || isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) 00946 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth token found in GET or POST when token present in header'); 00947 00948 $auth_header = trim($auth_header); 00949 00950 // Make sure it's Token authorization 00951 if (strcmp(substr($auth_header, 0, 5), "OAuth ") !== 0) 00952 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth header found that doesn\'t start with "OAuth"'); 00953 00954 // Parse the rest of the header 00955 if (preg_match('/\s*OAuth\s*="(.+)"/', substr($auth_header, 5), $matches) == 0 || count($matches) < 2) 00956 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Malformed auth header'); 00957 00958 return $matches[1]; 00959 } 00960 00961 if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME])) { 00962 if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) // Both GET and POST are not allowed 00963 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Only send the token in GET or POST, not both'); 00964 00965 return $_GET[OAUTH2_TOKEN_PARAM_NAME]; 00966 } 00967 00968 if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) 00969 return $_POST[OAUTH2_TOKEN_PARAM_NAME]; 00970 00971 return FALSE; 00972 } 00973 00974 // Access token granting (Section 4). 00975 00976 /** 00977 * Grant or deny a requested access token. 00978 * 00979 * This would be called from the "/token" endpoint as defined in the spec. 00980 * Obviously, you can call your endpoint whatever you want. 00981 * 00982 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4 00983 * 00984 * @ingroup oauth2_section_4 00985 */ 00986 public function grantAccessToken() { 00987 $filters = array( 00988 "grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), 00989 "scope" => array("flags" => FILTER_REQUIRE_SCALAR), 00990 "code" => array("flags" => FILTER_REQUIRE_SCALAR), 00991 "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), 00992 "username" => array("flags" => FILTER_REQUIRE_SCALAR), 00993 "password" => array("flags" => FILTER_REQUIRE_SCALAR), 00994 "assertion_type" => array("flags" => FILTER_REQUIRE_SCALAR), 00995 "assertion" => array("flags" => FILTER_REQUIRE_SCALAR), 00996 "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR), 00997 ); 00998 00999 $input = filter_input_array(INPUT_POST, $filters); 01000 01001 // Grant Type must be specified. 01002 if (!$input["grant_type"]) 01003 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); 01004 01005 // Make sure we've implemented the requested grant type 01006 if (!in_array($input["grant_type"], $this->getSupportedGrantTypes())) 01007 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE); 01008 01009 // Authorize the client 01010 $client = $this->getClientCredentials(); 01011 01012 if ($this->checkClientCredentials($client[0], $client[1]) === FALSE) 01013 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); 01014 01015 if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"])) 01016 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); 01017 01018 // Do the granting 01019 switch ($input["grant_type"]) { 01020 case OAUTH2_GRANT_TYPE_AUTH_CODE: 01021 if (!$input["code"] || !$input["redirect_uri"]) 01022 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); 01023 01024 $stored = $this->getAuthCode($input["code"]); 01025 01026 // Ensure that the input uri starts with the stored uri 01027 if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) 01028 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); 01029 01030 if ($stored["expires"] < time()) 01031 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); 01032 01033 break; 01034 case OAUTH2_GRANT_TYPE_USER_CREDENTIALS: 01035 if (!$input["username"] || !$input["password"]) 01036 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); 01037 01038 $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]); 01039 01040 if ($stored === FALSE) 01041 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); 01042 01043 break; 01044 case OAUTH2_GRANT_TYPE_ASSERTION: 01045 if (!$input["assertion_type"] || !$input["assertion"]) 01046 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); 01047 01048 $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]); 01049 01050 if ($stored === FALSE) 01051 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); 01052 01053 break; 01054 case OAUTH2_GRANT_TYPE_REFRESH_TOKEN: 01055 if (!$input["refresh_token"]) 01056 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); 01057 01058 $stored = $this->getRefreshToken($input["refresh_token"]); 01059 01060 if ($stored === NULL || $client[0] != $stored["client_id"]) 01061 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); 01062 01063 if ($stored["expires"] < time()) 01064 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); 01065 01066 // store the refresh token locally so we can delete it when a new refresh token is generated 01067 $this->setVariable('_old_refresh_token', $stored["token"]); 01068 01069 break; 01070 case OAUTH2_GRANT_TYPE_NONE: 01071 $stored = $this->checkNoneAccess($client[0]); 01072 01073 if ($stored === FALSE) 01074 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); 01075 } 01076 01077 // Check scope, if provided 01078 if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) 01079 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE); 01080 01081 if (!$input["scope"]) 01082 $input["scope"] = NULL; 01083 01084 $token = $this->createAccessToken($client[0], $input["scope"]); 01085 01086 $this->sendJsonHeaders(); 01087 echo json_encode($token); 01088 } 01089 01090 /** 01091 * Internal function used to get the client credentials from HTTP basic 01092 * auth or POST data. 01093 * 01094 * @return 01095 * A list containing the client identifier and password, for example 01096 * @code 01097 * return array( 01098 * $_POST["client_id"], 01099 * $_POST["client_secret"], 01100 * ); 01101 * @endcode 01102 * 01103 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2 01104 * 01105 * @ingroup oauth2_section_2 01106 */ 01107 protected function getClientCredentials() { 01108 if (isset($_SERVER["PHP_AUTH_USER"]) && $_POST && isset($_POST["client_id"])) 01109 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); 01110 01111 // Try basic auth 01112 if (isset($_SERVER["PHP_AUTH_USER"])) 01113 return array($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]); 01114 01115 // Try POST 01116 if ($_POST && isset($_POST["client_id"])) { 01117 if (isset($_POST["client_secret"])) 01118 return array($_POST["client_id"], $_POST["client_secret"]); 01119 01120 return array($_POST["client_id"], NULL); 01121 } 01122 01123 // No credentials were specified 01124 $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); 01125 } 01126 01127 // End-user/client Authorization (Section 3 of IETF Draft). 01128 01129 /** 01130 * Pull the authorization request data out of the HTTP request. 01131 * 01132 * @return 01133 * The authorization parameters so the authorization server can prompt 01134 * the user for approval if valid. 01135 * 01136 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 01137 * 01138 * @ingroup oauth2_section_3 01139 */ 01140 public function getAuthorizeParams() { 01141 $filters = array( 01142 "client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), 01143 "response_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_AUTH_RESPONSE_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), 01144 "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), 01145 "state" => array("flags" => FILTER_REQUIRE_SCALAR), 01146 "scope" => array("flags" => FILTER_REQUIRE_SCALAR), 01147 ); 01148 01149 $input = filter_input_array(INPUT_GET, $filters); 01150 01151 // Make sure a valid client id was supplied 01152 if (!$input["client_id"]) { 01153 if ($input["redirect_uri"]) 01154 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); 01155 01156 $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); // We don't have a good URI to use 01157 } 01158 01159 // redirect_uri is not required if already established via other channels 01160 // check an existing redirect URI against the one supplied 01161 $redirect_uri = $this->getRedirectUri($input["client_id"]); 01162 01163 // At least one of: existing redirect URI or input redirect URI must be specified 01164 if (!$redirect_uri && !$input["redirect_uri"]) 01165 $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); 01166 01167 // getRedirectUri() should return FALSE if the given client ID is invalid 01168 // this probably saves us from making a separate db call, and simplifies the method set 01169 if ($redirect_uri === FALSE) 01170 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); 01171 01172 // If there's an existing uri and one from input, verify that they match 01173 if ($redirect_uri && $input["redirect_uri"]) { 01174 // Ensure that the input uri starts with the stored uri 01175 if (strcasecmp(substr($input["redirect_uri"], 0, strlen($redirect_uri)), $redirect_uri) !== 0) 01176 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]); 01177 } 01178 elseif ($redirect_uri) { // They did not provide a uri from input, so use the stored one 01179 $input["redirect_uri"] = $redirect_uri; 01180 } 01181 01182 // type and client_id are required 01183 if (!$input["response_type"]) 01184 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]); 01185 01186 // Check requested auth response type against the list of supported types 01187 if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE) 01188 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]); 01189 01190 // Restrict clients to certain authorization response types 01191 if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE) 01192 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]); 01193 01194 // Validate that the requested scope is supported 01195 if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes())) 01196 $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]); 01197 01198 return $input; 01199 } 01200 01201 /** 01202 * Redirect the user appropriately after approval. 01203 * 01204 * After the user has approved or denied the access request the 01205 * authorization server should call this function to redirect the user 01206 * appropriately. 01207 * 01208 * @param $is_authorized 01209 * TRUE or FALSE depending on whether the user authorized the access. 01210 * @param $params 01211 * An associative array as below: 01212 * - response_type: The requested response: an access token, an 01213 * authorization code, or both. 01214 * - client_id: The client identifier as described in Section 2. 01215 * - redirect_uri: An absolute URI to which the authorization server 01216 * will redirect the user-agent to when the end-user authorization 01217 * step is completed. 01218 * - scope: (optional) The scope of the access request expressed as a 01219 * list of space-delimited strings. 01220 * - state: (optional) An opaque value used by the client to maintain 01221 * state between the request and callback. 01222 * 01223 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 01224 * 01225 * @ingroup oauth2_section_3 01226 */ 01227 public function finishClientAuthorization($is_authorized, $params = array()) { 01228 $params += array( 01229 'scope' => NULL, 01230 'state' => NULL, 01231 ); 01232 extract($params); 01233 01234 if ($state !== NULL) 01235 $result["query"]["state"] = $state; 01236 01237 if ($is_authorized === FALSE) { 01238 $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; 01239 } 01240 else { 01241 if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) 01242 $result["query"]["code"] = $this->createAuthCode($client_id, $redirect_uri, $scope); 01243 01244 if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) 01245 $result["fragment"] = $this->createAccessToken($client_id, $scope); 01246 } 01247 01248 $this->doRedirectUriCallback($redirect_uri, $result); 01249 } 01250 01251 // Other/utility functions. 01252 01253 /** 01254 * Redirect the user agent. 01255 * 01256 * Handle both redirect for success or error response. 01257 * 01258 * @param $redirect_uri 01259 * An absolute URI to which the authorization server will redirect 01260 * the user-agent to when the end-user authorization step is completed. 01261 * @param $params 01262 * Parameters to be pass though buildUri(). 01263 * 01264 * @ingroup oauth2_section_3 01265 */ 01266 private function doRedirectUriCallback($redirect_uri, $params) { 01267 header("HTTP/1.1 ". OAUTH2_HTTP_FOUND); 01268 header("Location: " . $this->buildUri($redirect_uri, $params)); 01269 exit; 01270 } 01271 01272 /** 01273 * Build the absolute URI based on supplied URI and parameters. 01274 * 01275 * @param $uri 01276 * An absolute URI. 01277 * @param $params 01278 * Parameters to be append as GET. 01279 * 01280 * @return 01281 * An absolute URI with supplied parameters. 01282 * 01283 * @ingroup oauth2_section_3 01284 */ 01285 private function buildUri($uri, $params) { 01286 $parse_url = parse_url($uri); 01287 01288 // Add our params to the parsed uri 01289 foreach ($params as $k => $v) { 01290 if (isset($parse_url[$k])) 01291 $parse_url[$k] .= "&" . http_build_query($v); 01292 else 01293 $parse_url[$k] = http_build_query($v); 01294 } 01295 01296 // Put humpty dumpty back together 01297 return 01298 ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "") 01299 . ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "") 01300 . ((isset($parse_url["host"])) ? $parse_url["host"] : "") 01301 . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "") 01302 . ((isset($parse_url["path"])) ? $parse_url["path"] : "") 01303 . ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "") 01304 . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : ""); 01305 } 01306 01307 /** 01308 * Handle the creation of access token, also issue refresh token if support. 01309 * 01310 * This belongs in a separate factory, but to keep it simple, I'm just 01311 * keeping it here. 01312 * 01313 * @param $client_id 01314 * Client identifier related to the access token. 01315 * @param $scope 01316 * (optional) Scopes to be stored in space-separated string. 01317 * 01318 * @ingroup oauth2_section_4 01319 */ 01320 protected function createAccessToken($client_id, $scope = NULL) { 01321 $token = array( 01322 "access_token" => $this->genAccessToken(), 01323 "expires_in" => $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), 01324 "scope" => $scope 01325 ); 01326 01327 $this->setAccessToken($token["access_token"], $client_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); 01328 01329 // Issue a refresh token also, if we support them 01330 if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { 01331 $token["refresh_token"] = $this->genAccessToken(); 01332 $this->setRefreshToken($token["refresh_token"], $client_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope); 01333 // If we've granted a new refresh token, expire the old one 01334 if ($this->getVariable('_old_refresh_token')) 01335 $this->unsetRefreshToken($this->getVariable('_old_refresh_token')); 01336 } 01337 01338 return $token; 01339 } 01340 01341 /** 01342 * Handle the creation of auth code. 01343 * 01344 * This belongs in a separate factory, but to keep it simple, I'm just 01345 * keeping it here. 01346 * 01347 * @param $client_id 01348 * Client identifier related to the access token. 01349 * @param $redirect_uri 01350 * An absolute URI to which the authorization server will redirect the 01351 * user-agent to when the end-user authorization step is completed. 01352 * @param $scope 01353 * (optional) Scopes to be stored in space-separated string. 01354 * 01355 * @ingroup oauth2_section_3 01356 */ 01357 private function createAuthCode($client_id, $redirect_uri, $scope = NULL) { 01358 $code = $this->genAuthCode(); 01359 $this->setAuthCode($code, $client_id, $redirect_uri, time() + $this->getVariable('auth_code_lifetime', OAUTH2_DEFAULT_AUTH_CODE_LIFETIME), $scope); 01360 return $code; 01361 } 01362 01363 /** 01364 * Generate unique access token. 01365 * 01366 * Implementing classes may want to override these function to implement 01367 * other access token or auth code generation schemes. 01368 * 01369 * @return 01370 * An unique access token. 01371 * 01372 * @ingroup oauth2_section_4 01373 */ 01374 protected function genAccessToken() { 01375 return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid()))); 01376 } 01377 01378 /** 01379 * Generate unique auth code. 01380 * 01381 * Implementing classes may want to override these function to implement 01382 * other access token or auth code generation schemes. 01383 * 01384 * @return 01385 * An unique auth code. 01386 * 01387 * @ingroup oauth2_section_3 01388 */ 01389 protected function genAuthCode() { 01390 return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid()))); 01391 } 01392 01393 /** 01394 * Pull out the Authorization HTTP header and return it. 01395 * 01396 * Implementing classes may need to override this function for use on 01397 * non-Apache web servers. 01398 * 01399 * @return 01400 * The Authorization HTTP header, and FALSE if does not exist. 01401 * 01402 * @todo Handle Authorization HTTP header for non-Apache web servers. 01403 * 01404 * @ingroup oauth2_section_5 01405 */ 01406 private function getAuthorizationHeader() { 01407 if (array_key_exists("HTTP_AUTHORIZATION", $_SERVER)) 01408 return $_SERVER["HTTP_AUTHORIZATION"]; 01409 01410 if (function_exists("apache_request_headers")) { 01411 $headers = apache_request_headers(); 01412 01413 if (array_key_exists("Authorization", $headers)) 01414 return $headers["Authorization"]; 01415 } 01416 01417 return FALSE; 01418 } 01419 01420 /** 01421 * Send out HTTP headers for JSON. 01422 * 01423 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.2 01424 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 01425 * 01426 * @ingroup oauth2_section_4 01427 */ 01428 private function sendJsonHeaders() { 01429 header("Content-Type: application/json"); 01430 header("Cache-Control: no-store"); 01431 } 01432 01433 /** 01434 * Redirect the end-user's user agent with error message. 01435 * 01436 * @param $redirect_uri 01437 * An absolute URI to which the authorization server will redirect the 01438 * user-agent to when the end-user authorization step is completed. 01439 * @param $error 01440 * A single error code as described in Section 3.2.1. 01441 * @param $error_description 01442 * (optional) A human-readable text providing additional information, 01443 * used to assist in the understanding and resolution of the error 01444 * occurred. 01445 * @param $error_uri 01446 * (optional) A URI identifying a human-readable web page with 01447 * information about the error, used to provide the end-user with 01448 * additional information about the error. 01449 * @param $state 01450 * (optional) REQUIRED if the "state" parameter was present in the client 01451 * authorization request. Set to the exact value received from the client. 01452 * 01453 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2 01454 * 01455 * @ingroup oauth2_error 01456 */ 01457 private function errorDoRedirectUriCallback($redirect_uri, $error, $error_description = NULL, $error_uri = NULL, $state = NULL) { 01458 $result["query"]["error"] = $error; 01459 01460 if ($state) 01461 $result["query"]["state"] = $state; 01462 01463 if ($this->getVariable('display_error') && $error_description) 01464 $result["query"]["error_description"] = $error_description; 01465 01466 if ($this->getVariable('display_error') && $error_uri) 01467 $result["query"]["error_uri"] = $error_uri; 01468 01469 $this->doRedirectUriCallback($redirect_uri, $result); 01470 } 01471 01472 /** 01473 * Send out error message in JSON. 01474 * 01475 * @param $http_status_code 01476 * HTTP status code message as predefined. 01477 * @param $error 01478 * A single error code. 01479 * @param $error_description 01480 * (optional) A human-readable text providing additional information, 01481 * used to assist in the understanding and resolution of the error 01482 * occurred. 01483 * @param $error_uri 01484 * (optional) A URI identifying a human-readable web page with 01485 * information about the error, used to provide the end-user with 01486 * additional information about the error. 01487 * 01488 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 01489 * 01490 * @ingroup oauth2_error 01491 */ 01492 private function errorJsonResponse($http_status_code, $error, $error_description = NULL, $error_uri = NULL) { 01493 $result['error'] = $error; 01494 01495 if ($this->getVariable('display_error') && $error_description) 01496 $result["error_description"] = $error_description; 01497 01498 if ($this->getVariable('display_error') && $error_uri) 01499 $result["error_uri"] = $error_uri; 01500 01501 header("HTTP/1.1 " . $http_status_code); 01502 $this->sendJsonHeaders(); 01503 echo json_encode($result); 01504 01505 exit; 01506 } 01507 01508 /** 01509 * Send a 401 unauthorized header with the given realm and an error, if 01510 * provided. 01511 * 01512 * @param $http_status_code 01513 * HTTP status code message as predefined. 01514 * @param $realm 01515 * The "realm" attribute is used to provide the protected resources 01516 * partition as defined by [RFC2617]. 01517 * @param $scope 01518 * A space-delimited list of scope values indicating the required scope 01519 * of the access token for accessing the requested resource. 01520 * @param $error 01521 * The "error" attribute is used to provide the client with the reason 01522 * why the access request was declined. 01523 * @param $error_description 01524 * (optional) The "error_description" attribute provides a human-readable text 01525 * containing additional information, used to assist in the understanding 01526 * and resolution of the error occurred. 01527 * @param $error_uri 01528 * (optional) The "error_uri" attribute provides a URI identifying a human-readable 01529 * web page with information about the error, used to offer the end-user 01530 * with additional information about the error. If the value is not an 01531 * absolute URI, it is relative to the URI of the requested protected 01532 * resource. 01533 * 01534 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2 01535 * 01536 * @ingroup oauth2_error 01537 */ 01538 private function errorWWWAuthenticateResponseHeader($http_status_code, $realm, $error, $error_description = NULL, $error_uri = NULL, $scope = NULL) { 01539 $realm = $realm === NULL ? $this->getDefaultAuthenticationRealm() : $realm; 01540 01541 $result = "WWW-Authenticate: OAuth realm='" . $realm . "'"; 01542 01543 if ($error) 01544 $result .= ", error='" . $error . "'"; 01545 01546 if ($this->getVariable('display_error') && $error_description) 01547 $result .= ", error_description='" . $error_description . "'"; 01548 01549 if ($this->getVariable('display_error') && $error_uri) 01550 $result .= ", error_uri='" . $error_uri . "'"; 01551 01552 if ($scope) 01553 $result .= ", scope='" . $scope . "'"; 01554 01555 header("HTTP/1.1 ". $http_status_code); 01556 header($result); 01557 01558 exit; 01559 } 01560 }
1.7.1