/**
  * 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;
 }
Example #2
0
 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;
 }