/** * Grant or deny a requested access token. * This would be called from the "/token" endpoint as defined in the spec. * Obviously, you can call your endpoint whatever you want. * * @param $inputData - The draft specifies that the parameters should be * retrieved from POST, but you can override to whatever method you like. * @throws OAuth2ServerException * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.6 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-4.1.3 * * @ingroup oauth2_section_4 */ public function grantAccessToken(array $inputData = NULL, array $authHeaders = NULL) { $filters = array("grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), "scope" => array("flags" => FILTER_REQUIRE_SCALAR), "code" => array("flags" => FILTER_REQUIRE_SCALAR), "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), "username" => array("flags" => FILTER_REQUIRE_SCALAR), "password" => array("flags" => FILTER_REQUIRE_SCALAR), "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR)); // Input data by default can be either POST or GET if (!isset($inputData)) { $inputData = $_SERVER['REQUEST_METHOD'] == 'POST' ? $_POST : $_GET; } // Basic authorization header $authHeaders = isset($authHeaders) ? $authHeaders : $this->getAuthorizationHeader(); // Filter input data $input = filter_var_array($inputData, $filters); // Grant Type must be specified. if (!$input["grant_type"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); } // Authorize the client $client = $this->getClientCredentials($inputData, $authHeaders); if ($this->storage->checkClientCredentials($client[0], $client[1]) === FALSE) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client credentials are invalid'); } if (!$this->storage->checkRestrictedGrantType($client[0], $input["grant_type"])) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id'); } // Do the granting switch ($input["grant_type"]) { case self::GRANT_TYPE_AUTH_CODE: if (!$this->storage instanceof IOAuth2GrantCode) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } if (!$input["code"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameter. "code" is required'); } if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, "The redirect URI parameter is required."); } $stored = $this->storage->getAuthCode($input["code"]); // Check the code exists if ($stored === NULL || $client[0] != $stored["client_id"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "Refresh token doesn't exist or is invalid for the client"); } // Validate the redirect URI. If a redirect URI has been provided on input, it must be validated if ($input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, "The redirect URI is missing or do not match"); } if ($stored["expires"] < time()) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "The authorization code has expired"); } break; case self::GRANT_TYPE_USER_CREDENTIALS: if (!$this->storage instanceof IOAuth2GrantUser) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } if (!$input["username"] || !$input["password"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); } $stored = $this->storage->checkUserCredentials($client[0], $input["username"], $input["password"]); if ($stored === FALSE) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); } break; case self::GRANT_TYPE_CLIENT_CREDENTIALS: if (!$this->storage instanceof IOAuth2GrantClient) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } if (empty($client[1])) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client_secret is mandatory for the "client_credentials" grant type'); } // NB: We don't need to check for $stored==false, because it was checked above already $stored = $this->storage->checkClientCredentialsGrant($client[0], $client[1]); break; case self::GRANT_TYPE_REFRESH_TOKEN: if (!$this->storage instanceof IOAuth2RefreshTokens) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } if (!$input["refresh_token"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); } $stored = $this->storage->getRefreshToken($input["refresh_token"]); if ($stored === NULL || $client[0] != $stored["client_id"]) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Invalid refresh token'); } if ($stored["expires"] < time()) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Refresh token has expired'); } // store the refresh token locally so we can delete it when a new refresh token is generated $this->oldRefreshToken = $stored["refresh_token"]; break; case self::GRANT_TYPE_IMPLICIT: /* TODO: NOT YET IMPLEMENTED */ throw new OAuth2ServerException('501 Not Implemented', 'This OAuth2 library is not yet complete. This functionality is not implemented yet.'); if (!$this->storage instanceof IOAuth2GrantImplicit) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } break; // Extended grant types: // Extended grant types: case filter_var($input["grant_type"], FILTER_VALIDATE_URL): if (!$this->storage instanceof IOAuth2GrantExtension) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE); } $uri = filter_var($input["grant_type"], FILTER_VALIDATE_URL); $stored = $this->storage->checkGrantExtension($uri, $inputData, $authHeaders); if ($stored === FALSE) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT); } break; default: throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); } if (!isset($stored["scope"])) { $stored["scope"] = NULL; } // Check scope, if provided if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.'); } $user_id = isset($stored['user_id']) ? $stored['user_id'] : null; $token = $this->createAccessToken($client[0], $user_id, $stored['scope']); // Send response $this->sendJsonHeaders(); echo json_encode($token); }