diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index b3a2d9ee70..188c0ec0e9 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -13,6 +13,9 @@ use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\Exception\AccountLockedException; use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException; +use Alchemy\Phrasea\Exception\RuntimeException; +use Alchemy\Phrasea\Model\Entities\ApiApplication; +use Alchemy\Phrasea\Model\Entities\Session; use Symfony\Component\HttpFoundation\Request; class API_OAuth2_Adapter extends OAuth2 @@ -24,7 +27,7 @@ class API_OAuth2_Adapter extends OAuth2 /** * - * @var API_OAuth2_Application + * @var ApiApplication */ protected $client; @@ -84,10 +87,8 @@ class API_OAuth2_Adapter extends OAuth2 public function __construct(Application $app) { parent::__construct(); - $this->params = []; $this->app = $app; - - return $this; + $this->params = []; } /** @@ -101,7 +102,7 @@ class API_OAuth2_Adapter extends OAuth2 /** * - * @return API_OAuth2_Application + * @return ApiApplication */ public function getClient() { @@ -125,11 +126,7 @@ class API_OAuth2_Adapter extends OAuth2 return $this->token; } - /** - * - * @param API_OAuth2_Application $client - */ - public function setClient(API_OAuth2_Application $client) + public function setClient(ApiApplication $client) { $this->client = $client; @@ -164,91 +161,85 @@ class API_OAuth2_Adapter extends OAuth2 } /** - * * Implements OAuth2::checkClientCredentials(). * - * @param string $client_id - * @param string $client_secret + * @param string $clientId + * @param string $clientSecret * @return boolean */ - protected function checkClientCredentials($client_id, $client_secret = NULL) + protected function checkClientCredentials($clientId, $clientSecret = null) { - try { - $application = API_OAuth2_Application::load_from_client_id($this->app, $client_id); - - if ($client_secret === NULL) { - return true; - } - - return ($application->get_client_secret() === $client_secret); - } catch (\Exception $e) { - + if (null === $application = $this->app['repo.api-applications']->findByClientId($clientId)) { + return false; } - return false; + if (null === $clientSecret) { + return true; + } + + return $application->getClientSecret() === $clientSecret; } /** * * Implements OAuth2::getRedirectUri(). * - * @param string $client_id - * @return string + * @param $clientId + * + * @return mixed + * @throws RuntimeException */ - protected function getRedirectUri($client_id) + protected function getRedirectUri($clientId) { - $application = API_OAuth2_Application::load_from_client_id($this->app, $client_id); + if (null === $application = $this->app['repo.api-applications']->findByClientId($clientId)) { + throw new RuntimeException(sprintf('Application with client id %s could not be found', $clientId)); + } - return $application->get_redirect_uri(); + return $application->getRedirectUri(); } /** * * Implements OAuth2::getAccessToken(). * - * @param string $oauth_token + * @param string $oauthToken * @return array */ - protected function getAccessToken($oauth_token) + protected function getAccessToken($oauthToken) { - $result = null; - - try { - $token = API_OAuth2_Token::load_by_oauth_token($this->app, $oauth_token); - - $result = [ - 'scope' => $token->get_scope() - , 'expires' => $token->get_expires() - , 'client_id' => $token->get_account()->get_application()->get_client_id() - , 'session_id' => $token->get_session_id() - , 'revoked' => ($token->get_account()->is_revoked() ? '1' : '0') - , 'usr_id' => $token->get_account()->get_user()->getId() - , 'oauth_token' => $token->get_value() - ]; - - } catch (\Exception $e) { - + if (null === $token = $this->app['repo.api-oauth-tokens']->find($oauthToken)) { + return null; } - return $result; + return [ + 'scope' => $token->getScope(), + 'expires' => $token->getExpire()->getTimestamp(), + 'client_id' => $token->getAccount()->getApplication()->getClientId(), + 'session_id' => null === $token->getSession() ? null : $token->getSession()->getId(), + 'revoked' => (int) $token->getAccount()->isRevoked(), + 'usr_id' => $token->getAccount()->getUser()->getId(), + 'oauth_token' => $token->getOauthToken(), + ]; } /** * Implements OAuth2::setAccessToken(). - */ - - /** * - * @param type $oauth_token - * @param type $account_id - * @param type $expires - * @param string $scope - * @return API_OAuth2_Adapter + * @param $oauthToken + * @param $accountId + * @param $expires + * @param null $scope + * + * @return $this + * @throws RuntimeException */ - protected function setAccessToken($oauth_token, $account_id, $expires, $scope = NULL) + protected function setAccessToken($oauthToken, $accountId, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - $token = API_OAuth2_Token::create($this->app['phraseanet.appbox'], $account, $this->app['random.medium'], $scope); - $token->set_value($oauth_token)->set_expires($expires); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $token = $this->app['manipulator.api-oauth-token']->create($account, null, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-token']->setOauthToken($token, $oauthToken); return $this; } @@ -279,87 +270,106 @@ class API_OAuth2_Adapter extends OAuth2 } /** - * * Overrides OAuth2::getAuthCode(). * - * @return array + * @param $code + * + * @return array|null */ protected function getAuthCode($code) { - try { - $code = new API_OAuth2_AuthCode($this->app, $code); - - return [ - 'redirect_uri' => $code->get_redirect_uri() - , 'client_id' => $code->get_account()->get_application()->get_client_id() - , 'expires' => $code->get_expires() - , 'account_id' => $code->get_account()->get_id() - ]; - } catch (\Exception $e) { - + if (null === $code = $this->app['repo.api-oauth-codes']->find($code)) { + return null; } - return null; + return [ + 'redirect_uri' => $code->getRedirectUri(), + 'client_id' => $code->getAccount()->getApplication()->getClientId(), + 'expires' => $code->getExpires()->getTimestamp(), + 'account_id' => $code->getAccount()->getId(), + ]; } /** * * Overrides OAuth2::setAuthCode(). + * + * @param $oauthCode + * @param $accountId + * @param $redirectUri + * @param $expires + * @param null $scope * - * @param string $code - * @param int $account_id - * @param string $redirect_uri - * @param string $expires - * @param string $scope - * @return API_OAuth2_Adapter + * @return $this|void + * @throws RuntimeException */ - protected function setAuthCode($code, $account_id, $redirect_uri, $expires, $scope = NULL) + protected function setAuthCode($oauthCode, $accountId, $redirectUri, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - $code = API_OAuth2_AuthCode::create($this->app, $account, $code, $expires); - $code->set_redirect_uri($redirect_uri)->set_scope($scope); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $code = $this->app['manipulator.api-oauth-code']->create($account, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-code']->setCode($code, $oauthCode); return $this; } /** * Overrides OAuth2::setRefreshToken(). + * + * @param $refreshToken + * @param $accountId + * @param $expires + * @param null $scope + * + * @return $this|void + * @throws RuntimeException */ - protected function setRefreshToken($refresh_token, $account_id, $expires, $scope = NULL) + protected function setRefreshToken($refreshToken, $accountId, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - API_OAuth2_RefreshToken::create($this->app, $account, $expires, $refresh_token, $scope); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $token = $this->app['manipulator.api-oauth-refresh-token']->create($account, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-refresh-token']->setRefreshToken($token, $refreshToken); return $this; } /** * Overrides OAuth2::getRefreshToken(). + * + * @param $refreshToken + * + * @return array|null */ - protected function getRefreshToken($refresh_token) + protected function getRefreshToken($refreshToken) { - try { - $token = new API_OAuth2_RefreshToken($this->app, $refresh_token); - - return [ - 'token' => $token->get_value() - , 'expires' => $token->get_expires()->format('U') - , 'client_id' => $token->get_account()->get_application()->get_client_id() - ]; - } catch (\Exception $e) { - + if (null === $token = $this->app['repo.api-oauth-refresh-token']->find($refreshToken)) { + return null; } - return null; + return [ + 'token' => $token->getRefreshToken(), + 'expires' => $token->getExpires()->getTimestamp(), + 'client_id' => $token->getAccount()->getApplication()->getClientId() + ]; } /** * Overrides OAuth2::unsetRefreshToken(). + * + * @param $refreshToken + * + * @return $this|void */ - protected function unsetRefreshToken($refresh_token) + protected function unsetRefreshToken($refreshToken) { - $token = new API_OAuth2_RefreshToken($this->app, $refresh_token); - $token->delete(); + if (null !== $token = $this->app['repo.api-oauth-refresh-token']->find($refreshToken)) { + $this->app['manipulator.api-oauth-refresh-token']->delete($token); + } return $this; } @@ -371,22 +381,21 @@ class API_OAuth2_Adapter extends OAuth2 */ public function getAuthorizationRequestParameters(Request $request) { - - $datas = [ - 'response_type' => $request->get('response_type', false) - , 'client_id' => $request->get('client_id', false) - , 'redirect_uri' => $request->get('redirect_uri', false) + $data = [ + 'response_type' => $request->get('response_type', false), + 'client_id' => $request->get('client_id', false), + 'redirect_uri' => $request->get('redirect_uri', false), ]; $scope = $request->get('scope', false); $state = $request->get('state', false); if ($state) { - $datas["state"] = $state; + $data["state"] = $state; } if ($scope) { - $datas["scope"] = $scope; + $data["scope"] = $scope; } $filters = [ @@ -405,17 +414,16 @@ class API_OAuth2_Adapter extends OAuth2 , "scope" => ["flags" => FILTER_REQUIRE_SCALAR] ]; - $input = filter_var_array($datas, $filters); + $input = filter_var_array($data, $filters); /** * check for valid client_id * check for valid redirect_uri */ if (! $input["client_id"]) { - if ($input["redirect_uri"]) - $this->errorDoRedirectUriCallback( - $input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"] - ); + if ($input["redirect_uri"]) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, null, null, $input["state"]); + } // We don't have a good URI to use $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); } @@ -424,68 +432,71 @@ class API_OAuth2_Adapter extends OAuth2 * redirect_uri is not required if already established via other channels * check an existing redirect URI against the one supplied */ - $redirect_uri = $this->getRedirectUri($input["client_id"]); + $redirectUri = $this->getRedirectUri($input["client_id"]); /** * At least one of: existing redirect URI or input redirect URI must be specified */ - if ( ! $redirect_uri && ! $input["redirect_uri"]) - $this->errorJsonResponse( - OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); - + if ( ! $redirectUri && ! $input["redirect_uri"]) { + $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); + } /** - * getRedirectUri() should return FALSE if the given client ID is invalid + * getRedirectUri() should return false if the given client ID is invalid * this probably saves us from making a separate db call, and simplifies the method set */ - if ($redirect_uri === FALSE) - $this->errorDoRedirectUriCallback( - $input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); + if ($redirectUri === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, null, null, $input["state"]); + } /** * If there's an existing uri and one from input, verify that they match */ - if ($redirect_uri && $input["redirect_uri"]) { + if ($redirectUri && $input["redirect_uri"]) { /** * Ensure that the input uri starts with the stored uri */ $compare = strcasecmp( substr( - $input["redirect_uri"], 0, strlen($redirect_uri) - ), $redirect_uri); - if ($compare !== 0) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]); - } elseif ($redirect_uri) { + $input["redirect_uri"], 0, strlen($redirectUri) + ), $redirectUri); + if ($compare !== 0) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, null, null, $input["state"]); + } + } elseif ($redirectUri) { /** * They did not provide a uri from input, so use the stored one */ - $input["redirect_uri"] = $redirect_uri; + $input["redirect_uri"] = $redirectUri; } /** * Check response_type */ if (! $input["response_type"]) { - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]); + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', null, $input["state"]); } /** * Check requested auth response type against the list of supported types */ - if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]); + if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, null, null, $input["state"]); + } /** * Restrict clients to certain authorization response types */ - if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]); + if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, null, null, $input["state"]); + } /** * Validate that the requested scope is supported */ - if ($input["scope"] && ! $this->checkScope($input["scope"], $this->getSupportedScopes())) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]); + if ($input["scope"] && ! $this->checkScope($input["scope"], $this->getSupportedScopes())) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, null, null, $input["state"]); + } /** * at this point all params are ok @@ -496,121 +507,101 @@ class API_OAuth2_Adapter extends OAuth2 } /** + * @param User $user * - * @param User $user - * @return API_OAuth2_Account + * @return mixed + * @throws logicalException */ public function updateAccount(User $user) { - if ($this->client === null) + if ($this->client === null) { throw new logicalException("Client property must be set before update an account"); + } - try { - $account = API_OAuth2_Account::load_with_user($this->app, $this->client, $user); - } catch (\Exception $e) { - $account = $this->createAccount($user->getId()); + if (null === $account = $this->app['repo.api-accounts']->findByUserAndApplication($user, $this->client)) { + $account = $this->app['manipulator.api-account']->create($this->client, $user); } return $account; } /** + * @param $is_authorized + * @param array $params * - * @param int $usr_id - * @return API_OAuth2_Account - */ - private function createAccount($usr_id) - { - $user = $this->app['repo.users']->find($usr_id); - - return API_OAuth2_Account::create($this->app, $user, $this->client); - } - - /** - * - * @param $is_authorized - * @param array $params - * @return string + * @return array */ public function finishNativeClientAuthorization($is_authorized, $params = []) { $result = []; - $params += [ - 'scope' => NULL, - 'state' => NULL, - ]; - extract($params); + $params += ['scope' => null, 'state' => null,]; - if ($state !== NULL) - $result["query"]["state"] = $state; + if ($params['state'] !== null) { + $result["query"]["state"] = $params['state'] ; + } - if ($is_authorized === FALSE) { + if ($is_authorized === false) { $result["error"] = OAUTH2_ERROR_USER_DENIED; } else { - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE) - $result["code"] = $this->createAuthCode($account_id, $redirect_uri, $scope); + if ($params['response_type'] === OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE) { + $result["code"] = $this->createAuthCode($params['account_id'], $params['redirect_uri'], $params['scope']); + } - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN) + if ($params['response_type'] === OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN) { $result["error"] = OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE; + } } return $result; } /** + * @param $redirectUri * - * @param $redirect_uri - * @return + * @return bool */ - public function isNativeApp($redirect_uri) + public function isNativeApp($redirectUri) { - return $redirect_uri === API_OAuth2_Application::NATIVE_APP_REDIRECT_URI; + return $redirectUri === ApiApplication::NATIVE_APP_REDIRECT_URI; } - public function remember_this_ses_id($ses_id) + public function rememberSession(Session $session) { - try { - $token = API_OAuth2_Token::load_by_oauth_token($this->app, $this->token); - $token->set_session_id($ses_id); - - return true; - } catch (\Exception $e) { - + if (null !== $token = $this->app['repo.api-oauth-tokens']->find($this->token)) { + $token->setSession($session); } - - return false; } - public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) + public function verifyAccessToken($scope = null, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = null) { $token_param = $this->getAccessTokenParams(); // Access token was not provided if ($token_param === false) { - 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; + 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; } // Get the stored token data (from the implementing subclass) $token = $this->getAccessToken($token_param); - if ($token === NULL) { - return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE; + if ($token === null) { + return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', null, $scope) : false; } if (isset($token['revoked']) && $token['revoked']) { - return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'End user has revoked access to his personal datas for your application.', NULL, $scope) : FALSE; + return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'End user has revoked access to his personal datas for your application.', null, $scope) : false; } if ($this->enable_expire) { // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages) if (isset($token["expires"]) && time() > $token["expires"]) { - return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE; + return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', null, $scope) : false; } } // Check scope, if provided - // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error + // If token doesn't have a scope, it's null/empty, or it's insufficient, then throw an error if ($scope && ( ! isset($token["scope"]) || ! $token["scope"] || ! $this->checkScope($scope, $token["scope"]))) { - 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; + 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; } //save token's linked ses_id $this->session_id = $token['session_id']; @@ -623,23 +614,25 @@ class API_OAuth2_Adapter extends OAuth2 public function finishClientAuthorization($is_authorized, $params = []) { $params += [ - 'scope' => NULL, - 'state' => NULL, + 'scope' => null, + 'state' => null, ]; - extract($params); - if ($state !== NULL) - $result["query"]["state"] = $state; - if ($is_authorized === FALSE) - $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; - else { - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) - $result["query"]["code"] = $this->createAuthCode($account_id, $redirect_uri, $scope); - - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) - $result["fragment"] = $this->createAccessToken($account_id, $scope); + if ($params['state'] !== null) { + $result["query"]["state"] = $params['state']; } - $this->doRedirectUriCallback($redirect_uri, $result); + if ($is_authorized === false) { + $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; + } else { + if ($params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) { + $result["query"]["code"] = $this->createAuthCode($params['account_id'], $params['redirect_uri'], $params['scope']); + } + + if ($params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) { + $result["fragment"] = $this->createAccessToken($params['account_id'], $params['scope']); + } + } + $this->doRedirectUriCallback($params['redirect_uri'], $result); } /** @@ -648,62 +641,75 @@ class API_OAuth2_Adapter extends OAuth2 public function grantAccessToken() { $filters = [ - "grant_type" => ["filter" => FILTER_VALIDATE_REGEXP, "options" => ["regexp" => OAUTH2_GRANT_TYPE_REGEXP], "flags" => FILTER_REQUIRE_SCALAR], - "scope" => ["flags" => FILTER_REQUIRE_SCALAR], - "code" => ["flags" => FILTER_REQUIRE_SCALAR], - "redirect_uri" => ["filter" => FILTER_SANITIZE_URL], - "username" => ["flags" => FILTER_REQUIRE_SCALAR], - "password" => ["flags" => FILTER_REQUIRE_SCALAR], - "assertion_type" => ["flags" => FILTER_REQUIRE_SCALAR], - "assertion" => ["flags" => FILTER_REQUIRE_SCALAR], + "grant_type" => [ + "filter" => FILTER_VALIDATE_REGEXP, + "options" => ["regexp" => OAUTH2_GRANT_TYPE_REGEXP], + "flags" => FILTER_REQUIRE_SCALAR + ], + "scope" => ["flags" => FILTER_REQUIRE_SCALAR], + "code" => ["flags" => FILTER_REQUIRE_SCALAR], + "redirect_uri" => ["filter" => FILTER_SANITIZE_URL], + "username" => ["flags" => FILTER_REQUIRE_SCALAR], + "password" => ["flags" => FILTER_REQUIRE_SCALAR], + "assertion_type" => ["flags" => FILTER_REQUIRE_SCALAR], + "assertion" => ["flags" => FILTER_REQUIRE_SCALAR], "refresh_token" => ["flags" => FILTER_REQUIRE_SCALAR], ]; $input = filter_input_array(INPUT_POST, $filters); // Grant Type must be specified. - if ( ! $input["grant_type"]) + if ( ! $input["grant_type"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); + } // Make sure we've implemented the requested grant type - if ( ! in_array($input["grant_type"], $this->getSupportedGrantTypes())) + if ( ! in_array($input["grant_type"], $this->getSupportedGrantTypes())) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE); + } // Authorize the client $client = $this->getClientCredentials(); - if ($this->checkClientCredentials($client[0], $client[1]) === FALSE) + if ($this->checkClientCredentials($client[0], $client[1]) === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); + } - if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) + if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); + } - if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) + if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); + } // Do the granting switch ($input["grant_type"]) { case OAUTH2_GRANT_TYPE_AUTH_CODE: - if ( ! $input["code"] || ! $input["redirect_uri"]) + if ( ! $input["code"] || ! $input["redirect_uri"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } $stored = $this->getAuthCode($input["code"]); // Ensure that the input uri starts with the stored uri - if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) + if ($stored === null || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } - if ($stored["expires"] < time()) + if ($stored["expires"] < time()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); + } break; case OAUTH2_GRANT_TYPE_USER_CREDENTIALS: - $application = API_OAuth2_Application::load_from_client_id($this->app, $client[0]); + $application = ApiApplication::load_from_client_id($this->app, $client[0]); if ( ! $application->is_password_granted()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE, 'Password grant type is not enable for your client'); } - if ( ! $input["username"] || ! $input["password"]) + if ( ! $input["username"] || ! $input["password"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); + } $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]); @@ -712,26 +718,31 @@ class API_OAuth2_Adapter extends OAuth2 } break; case OAUTH2_GRANT_TYPE_ASSERTION: - if ( ! $input["assertion_type"] || ! $input["assertion"]) + if ( ! $input["assertion_type"] || ! $input["assertion"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]); - if ($stored === FALSE) + if ($stored === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } break; case OAUTH2_GRANT_TYPE_REFRESH_TOKEN: - if ( ! $input["refresh_token"]) + if ( ! $input["refresh_token"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); + } $stored = $this->getRefreshToken($input["refresh_token"]); - if ($stored === NULL || $client[0] != $stored["client_id"]) + if ($stored === null || $client[0] != $stored["client_id"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } - if ($stored["expires"] < time()) + if ($stored["expires"] < time()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); + } // store the refresh token locally so we can delete it when a new refresh token is generated $this->setVariable('_old_refresh_token', $stored["token"]); @@ -740,16 +751,19 @@ class API_OAuth2_Adapter extends OAuth2 case OAUTH2_GRANT_TYPE_NONE: $stored = $this->checkNoneAccess($client[0]); - if ($stored === FALSE) + if ($stored === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } } // Check scope, if provided - if ($input["scope"] && ( ! is_array($stored) || ! isset($stored["scope"]) || ! $this->checkScope($input["scope"], $stored["scope"]))) + if ($input["scope"] && ( ! is_array($stored) || ! isset($stored["scope"]) || ! $this->checkScope($input["scope"], $stored["scope"]))) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE); + } - if ( ! $input["scope"]) - $input["scope"] = NULL; + if ( ! $input["scope"]) { + $input["scope"] = null; + } $token = $this->createAccessToken($stored['account_id'], $input["scope"]); $this->sendJsonHeaders(); @@ -759,51 +773,63 @@ class API_OAuth2_Adapter extends OAuth2 return; } - protected function createAccessToken($account_id, $scope = NULL) + protected function createAccessToken($accountId, $scope = null) { $token = [ "access_token" => $this->genAccessToken(), "scope" => $scope ]; - if ($this->enable_expire) + if ($this->enable_expire) { $token['expires_in'] = $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); + } - $this->setAccessToken($token["access_token"], $account_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); + $this->setAccessToken($token["access_token"], $accountId, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); // Issue a refresh token also, if we support them if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { $token["refresh_token"] = $this->genAccessToken(); - $this->setRefreshToken($token["refresh_token"], $account_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope); + $this->setRefreshToken($token["refresh_token"], $accountId, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope); // If we've granted a new refresh token, expire the old one - if ($this->getVariable('_old_refresh_token')) + if ($this->getVariable('_old_refresh_token')) { $this->unsetRefreshToken($this->getVariable('_old_refresh_token')); + } } return $token; } - protected function checkUserCredentials($client_id, $username, $password) + /** + * @param $clientId + * @param $username + * @param $password + * + * @return array|boolean + */ + protected function checkUserCredentials($clientId, $username, $password) { try { - $this->setClient(API_OAuth2_Application::load_from_client_id($this->app, $client_id)); + if (null === $client = $this->app['repo.api-applications']->findByClientId($clientId)) { + return false; + } + $this->setClient($client); - $usr_id = $this->app['auth.native']->getUsrId($username, $password, Request::createFromGlobals()); + $usrId = $this->app['auth.native']->getUsrId($username, $password, Request::createFromGlobals()); - if (!$usr_id) { + if (!$usrId) { return false; } - if (null === $user = $this->app['repo.users']->find($usr_id)) { + if (null === $user = $this->app['repo.users']->find($usrId)) { return false; } $account = $this->updateAccount($user); return [ - 'redirect_uri' => $this->client->get_redirect_uri() - , 'client_id' => $this->client->get_client_id() - , 'account_id' => $account->get_id() + 'redirect_uri' => $this->client->getRedirectUri(), + 'client_id' => $this->client->getClient(), + 'account_id' => $account->getId(), ]; } catch (AccountLockedException $e) { return false;