/** * Implementation of https://tools.ietf.org/html/draft-richer-oauth-introspection */ private function _introspectToken(array $param) { $r = array(); $token = Utils::getParameter($param, 'token'); if (NULL === $token) { throw new TokenIntrospectionException("invalid_token", "the token parameter is missing"); } $accessToken = $this->_storage->getAccessToken($token); if (FALSE === $accessToken) { // token does not exist $r['active'] = FALSE; } elseif (time() > $accessToken['issue_time'] + $accessToken['expires_in']) { // token expired $r['active'] = FALSE; } else { // token exists and did not expire $r['active'] = TRUE; $r['exp'] = intval($accessToken['issue_time'] + $accessToken['expires_in']); $r['iat'] = intval($accessToken['issue_time']); $r['scope'] = $accessToken['scope']; $r['client_id'] = $accessToken['client_id']; $r['sub'] = $accessToken['resource_owner_id']; $r['token_type'] = 'bearer'; // as long as we have no RS registration we cannot set the audience... // $response['aud'] = 'foo'; // add proprietary "x-entitlement" $resourceOwner = $this->_storage->getResourceOwner($accessToken['resource_owner_id']); if (isset($resourceOwner['entitlement'])) { $e = Json::dec($resourceOwner['entitlement']); if (0 !== count($e)) { $r['x-entitlement'] = $e; } } // add proprietary "x-ext" if (isset($resourceOwner['ext'])) { $e = Json::dec($resourceOwner['ext']); if (0 !== count($e)) { $r['x-ext'] = $e; } } } return $r; }
public function handleRequest(HttpRequest $request) { $response = new HttpResponse(200, "application/json"); try { if (!$this->_config->getSectionValue("Api", "enableApi")) { throw new ApiException("forbidden", "api disabled"); } $this->_rs->verifyAuthorizationHeader($request->getHeader("Authorization")); $storage = $this->_storage; // FIXME: can this be avoided?? $rs = $this->_rs; // FIXME: can this be avoided?? $request->matchRest("POST", "/authorizations/", function () use($request, $response, $storage, $rs) { $rs->requireScope("authorizations"); $data = Json::dec($request->getContent()); if (NULL === $data || !is_array($data) || !array_key_exists("client_id", $data) || !array_key_exists("scope", $data)) { throw new ApiException("invalid_request", "missing required parameters"); } // client needs to exist $clientId = $data['client_id']; $client = $storage->getClient($clientId); if (FALSE === $client) { throw new ApiException("invalid_request", "client is not registered"); } // scope should be part of "allowed_scope" of client registration $clientAllowedScope = new Scope($client['allowed_scope']); $requestedScope = new Scope($data['scope']); if (!$requestedScope->isSubSetOf($clientAllowedScope)) { throw new ApiException("invalid_request", "invalid scope for this client"); } $refreshToken = array_key_exists("refresh_token", $data) && $data['refresh_token'] ? Utils::randomHex(16) : NULL; // check to see if an authorization for this client/resource_owner already exists if (FALSE === $storage->getApprovalByResourceOwnerId($clientId, $rs->getResourceOwnerId())) { if (FALSE === $storage->addApproval($clientId, $rs->getResourceOwnerId(), $data['scope'], $refreshToken)) { throw new ApiException("invalid_request", "unable to add authorization"); } } else { throw new ApiException("invalid_request", "authorization already exists for this client and resource owner"); } $response->setStatusCode(201); $response->setContent(Json::enc(array("ok" => true))); }); $request->matchRest("GET", "/authorizations/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("authorizations"); $data = $storage->getApprovalByResourceOwnerId($id, $rs->getResourceOwnerId()); if (FALSE === $data) { throw new ApiException("not_found", "the resource you are trying to retrieve does not exist"); } $response->setContent(Json::enc($data)); }); $request->matchRest("GET", "/authorizations/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("authorizations"); $data = $storage->getApprovalByResourceOwnerId($id, $rs->getResourceOwnerId()); if (FALSE === $data) { throw new ApiException("not_found", "the resource you are trying to retrieve does not exist"); } $response->setContent(Json::enc($data)); }); $request->matchRest("DELETE", "/authorizations/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("authorizations"); if (FALSE === $storage->deleteApproval($id, $rs->getResourceOwnerId())) { throw new ApiException("not_found", "the resource you are trying to delete does not exist"); } $response->setContent(Json::enc(array("ok" => true))); }); $request->matchRest("GET", "/authorizations/", function () use($request, $response, $storage, $rs) { $rs->requireScope("authorizations"); $data = $storage->getApprovals($rs->getResourceOwnerId()); $response->setContent(Json::enc($data)); }); $request->matchRest("GET", "/applications/", function () use($request, $response, $storage, $rs) { $rs->requireScope("applications"); // $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); // do not require entitlement to list clients... $data = $storage->getClients(); $response->setContent(Json::enc($data)); }); $request->matchRest("DELETE", "/applications/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("applications"); $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); if (FALSE === $storage->deleteClient($id)) { throw new ApiException("not_found", "the resource you are trying to delete does not exist"); } $response->setContent(Json::enc(array("ok" => true))); }); $request->matchRest("GET", "/applications/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("applications"); $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); // FIXME: for now require entitlement as long as password hashing is not // implemented... $data = $storage->getClient($id); if (FALSE === $data) { throw new ApiException("not_found", "the resource you are trying to retrieve does not exist"); } $response->setContent(Json::enc($data)); }); $request->matchRest("POST", "/applications/", function () use($request, $response, $storage, $rs) { $rs->requireScope("applications"); $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); try { $client = ClientRegistration::fromArray(Json::dec($request->getContent())); $data = $client->getClientAsArray(); // check to see if an application with this id already exists if (FALSE === $storage->getClient($data['id'])) { if (FALSE === $storage->addClient($data)) { throw new ApiException("invalid_request", "unable to add application"); } } else { throw new ApiException("invalid_request", "application already exists"); } $response->setStatusCode(201); $response->setContent(Json::enc(array("ok" => true))); } catch (ClientRegistrationException $e) { throw new ApiException("invalid_request", $e->getMessage()); } }); $request->matchRest("GET", "/stats/", function () use($request, $response, $storage, $rs) { $rs->requireScope("applications"); $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); $data = $storage->getStats(); $response->setContent(Json::enc($data)); }); $request->matchRest("PUT", "/applications/:id", function ($id) use($request, $response, $storage, $rs) { $rs->requireScope("applications"); $rs->requireEntitlement("urn:x-oauth:entitlement:applications"); try { $client = ClientRegistration::fromArray(Json::dec($request->getContent())); $data = $client->getClientAsArray(); if ($data['id'] !== $id) { throw new ApiException("invalid_request", "resource does not match client id value"); } if (FALSE === $storage->updateClient($id, $data)) { throw new ApiException("invalid_request", "unable to update application"); } } catch (ClientRegistrationException $e) { throw new ApiException("invalid_request", $e->getMessage()); } $response->setContent(Json::enc(array("ok" => true))); }); $request->matchRestDefault(function ($methodMatch, $patternMatch) use($request, $response) { if (in_array($request->getRequestMethod(), $methodMatch)) { if (!$patternMatch) { throw new ApiException("not_found", "resource not found"); } } else { $response->setStatusCode(405); $response->setHeader("Allow", implode(",", $methodMatch)); } }); } catch (ResourceServerException $e) { $response->setStatusCode($e->getResponseCode()); if ("no_token" === $e->getMessage()) { // no authorization header is a special case, the client did not know // authentication was required, so tell it now without giving error message $hdr = 'Bearer realm="Resource Server"'; } else { $hdr = sprintf('Bearer realm="Resource Server",error="%s",error_description="%s"', $e->getMessage(), $e->getDescription()); } $response->setHeader("WWW-Authenticate", $hdr); $response->setContent(Json::enc(array("error" => $e->getMessage(), "error_description" => $e->getDescription()))); if (NULL !== $this->_logger) { $this->_logger->logFatal($e->getLogMessage(TRUE) . PHP_EOL . $request . PHP_EOL . $response); } } catch (ApiException $e) { $response->setStatusCode($e->getResponseCode()); $response->setContent(Json::enc(array("error" => $e->getMessage(), "error_description" => $e->getDescription()))); if (NULL !== $this->_logger) { $this->_logger->logFatal($e->getLogMessage(TRUE) . PHP_EOL . $request . PHP_EOL . $response); } } return $response; }
private function _handleApprove(IResourceOwner $resourceOwner, array $get, array $post) { try { $clientId = Utils::getParameter($get, 'client_id'); $responseType = Utils::getParameter($get, 'response_type'); $redirectUri = Utils::getParameter($get, 'redirect_uri'); $scope = new Scope(Utils::getParameter($get, 'scope')); $state = Utils::getParameter($get, 'state'); $result = $this->_handleAuthorize($resourceOwner, $get); if (AuthorizeResult::ASK_APPROVAL !== $result->getAction()) { return $result; } $approval = Utils::getParameter($post, 'approval'); // FIXME: are we sure this client is always valid? $client = $this->_storage->getClient($clientId); if ("approve" === $approval) { $approvedScope = $this->_storage->getApprovalByResourceOwnerId($clientId, $resourceOwner->getId()); if (FALSE === $approvedScope) { // no approved scope stored yet, new entry $refreshToken = "code" === $responseType ? Utils::randomHex(16) : NULL; $this->_storage->addApproval($clientId, $resourceOwner->getId(), $scope->getScope(), $refreshToken); } else { $this->_storage->updateApproval($clientId, $resourceOwner->getId(), $scope->getScope()); } return $this->_handleAuthorize($resourceOwner, $get); } else { throw new ClientException("access_denied", "not authorized by resource owner", $client, $state); } } catch (ScopeException $e) { throw new ClientException("invalid_scope", "malformed scope", $client, $state); } }
private function _handleToken(array $post, $user = NULL, $pass = NULL) { // exchange authorization code for access token $grantType = Utils::getParameter($post, 'grant_type'); $code = Utils::getParameter($post, 'code'); $redirectUri = Utils::getParameter($post, 'redirect_uri'); $refreshToken = Utils::getParameter($post, 'refresh_token'); $token = Utils::getParameter($post, 'token'); $clientId = Utils::getParameter($post, 'client_id'); $scope = Utils::getParameter($post, 'scope'); if (NULL !== $user && !empty($user) && NULL !== $pass && !empty($pass)) { // client provided authentication, it MUST be valid now... $client = $this->_storage->getClient($user); if (FALSE === $client) { throw new TokenException("invalid_client", "client authentication failed"); } // check pass if ($pass !== $client['secret']) { throw new TokenException("invalid_client", "client authentication failed"); } // if client_id in POST is set, it must match the user if (NULL !== $clientId && $clientId !== $user) { throw new TokenException("invalid_grant", "client_id inconsistency: authenticating user must match POST body client_id"); } $hasAuthenticated = TRUE; } else { // client provided no authentication, client_id must be in POST body if (NULL === $clientId || empty($clientId)) { throw new TokenException("invalid_request", "no client authentication used nor client_id POST parameter"); } $client = $this->_storage->getClient($clientId); if (FALSE === $client) { throw new TokenException("invalid_client", "client identity could not be established"); } $hasAuthenticated = FALSE; } if ("user_agent_based_application" === $client['type']) { throw new TokenException("unauthorized_client", "this client type is not allowed to use the token endpoint"); } if ("web_application" === $client['type'] && !$hasAuthenticated) { // web_application type MUST have authenticated throw new TokenException("invalid_client", "client authentication failed"); } if (NULL === $grantType) { throw new TokenException("invalid_request", "the grant_type parameter is missing"); } switch ($grantType) { case "authorization_code": if (NULL === $code) { throw new TokenException("invalid_request", "the code parameter is missing"); } // If the redirect_uri was present in the authorize request, it MUST also be there // in the token request. If it was not there in authorize request, it MUST NOT be // there in the token request (this is not explicit in the spec!) $result = $this->_storage->getAuthorizationCode($client['id'], $code, $redirectUri); if (FALSE === $result) { throw new TokenException("invalid_grant", "the authorization code was not found"); } if (time() > $result['issue_time'] + 600) { throw new TokenException("invalid_grant", "the authorization code expired"); } // we MUST be able to delete the authorization code, otherwise it was used before if (FALSE === $this->_storage->deleteAuthorizationCode($client['id'], $code, $redirectUri)) { // check to prevent deletion race condition throw new TokenException("invalid_grant", "this authorization code grant was already used"); } $approval = $this->_storage->getApprovalByResourceOwnerId($client['id'], $result['resource_owner_id']); $token = array(); $token['access_token'] = Utils::randomHex(16); $token['expires_in'] = intval($this->_config->getValue('accessTokenExpiry')); // we always grant the scope the user authorized, no further restrictions here... // FIXME: the merging of authorized scopes in the authorize function is a bit of a mess! // we should deal with that there and come up with a good solution... $token['scope'] = $result['scope']; $token['refresh_token'] = $approval['refresh_token']; $token['token_type'] = "bearer"; $this->_storage->storeAccessToken($token['access_token'], time(), $client['id'], $result['resource_owner_id'], $token['scope'], $token['expires_in']); break; case "refresh_token": if (NULL === $refreshToken) { throw new TokenException("invalid_request", "the refresh_token parameter is missing"); } $result = $this->_storage->getApprovalByRefreshToken($client['id'], $refreshToken); if (FALSE === $result) { throw new TokenException("invalid_grant", "the refresh_token was not found"); } $token = array(); $token['access_token'] = Utils::randomHex(16); $token['expires_in'] = intval($this->_config->getValue('accessTokenExpiry')); if (NULL !== $scope) { // the client wants to obtain a specific scope $requestedScope = new Scope($scope); $authorizedScope = new Scope($result['scope']); if ($requestedScope->isSubsetOf($authorizedScope)) { // if it is a subset of the authorized scope we honor that $token['scope'] = $requestedScope->getScope(); } else { // if not the client gets the authorized scope $token['scope'] = $result['scope']; } } else { $token['scope'] = $result['scope']; } $token['token_type'] = "bearer"; $this->_storage->storeAccessToken($token['access_token'], time(), $client['id'], $result['resource_owner_id'], $token['scope'], $token['expires_in']); break; default: throw new TokenException("unsupported_grant_type", "the requested grant type is not supported"); } return $token; }