diff --git a/README.md b/README.md index af3f84f..c3f4c1d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Please note that until I reach 1.0, I **WILL NOT** follow semantic version. This Installation is only officially supported using Composer: ```sh -php composer.phar require zfr/zfr-oauth2-server:0.1.* +php composer.phar require zfr/zfr-oauth2-server:0.2.* ``` ## Framework integration @@ -132,24 +132,18 @@ $response = $authorizationServer->handleRequest($request, $user); ### Using the resource server You can use the resource server to retrieve the access token (by automatically extracting the data from the HTTP -headers). You can also use the resource server to validate the access token against scopes: +headers). You can also specify scope constraints when retrieving the token: ```php $accessTokenService = new TokenService($objectManager, $accessTokenRepository, $scopeRepository); $resourceServer = new ResourceServer($accessTokenService); -if (!$resourceServer->isRequestValid($request, ['write']) { +if (!$token = $resourceServer->getAccessToken($request, ['write']) { // there is either no access token, or the access token is expired, or the access token does not have // the `write` scope } ``` -You can also manually retrieve the access token: - -```php -$accessToken = $resourceServer->getAccessToken($request); -``` - ### Doctrine ZfrOAuth2Server is built to be used with Doctrine (either ORM or ODM). Out of the box, it provides ORM mapping for diff --git a/src/ZfrOAuth2/Server/ResourceServer.php b/src/ZfrOAuth2/Server/ResourceServer.php index 55a0510..5e9134a 100644 --- a/src/ZfrOAuth2/Server/ResourceServer.php +++ b/src/ZfrOAuth2/Server/ResourceServer.php @@ -49,40 +49,39 @@ public function __construct(TokenService $accessTokenService) } /** - * Check if the request is valid + * Get the access token * - * If the scope parameter is given, it will also check that the token has enough permissions + * Note that this method will only match tokens that are not expired and match the given scopes (if any). + * Otherwise, null will be returned * - * @param HttpRequest $request - * @param array|string $scopes - * @return bool + * @link http://tools.ietf.org/html/rfc6750#page-5 + * @param HttpRequest $request + * @param array $scopes + * @return AccessToken|null */ - public function isRequestValid(HttpRequest $request, $scopes = []) + public function getAccessToken(HttpRequest $request, $scopes = []) { - // We extract the token and get the actual instance from storage - $accessToken = $this->getAccessToken($request); - - // It must exist and must not be outdated, otherwise it's wrong! - if (null === $accessToken || $accessToken->isExpired()) { - return false; + if (!$token = $this->extractAccessToken($request)) { + return null; } - if (!empty($scopes) && !$accessToken->matchScopes($scopes)) { - return false; + $token = $this->accessTokenService->getToken($token); + + if ($token === null || !$this->isTokenValid($token, $scopes)) { + return null; } - return true; + return $token; } /** - * Extract the access token from the Authorization header of the request + * Extract the token either from Authorization header or query params * - * @link http://tools.ietf.org/html/rfc6750#page-5 * @param HttpRequest $request - * @return AccessToken|null - * @throws InvalidAccessTokenException If no access token could be found + * @return string|null + * @throws Exception\InvalidAccessTokenException If access token is malformed in the Authorization header */ - public function getAccessToken(HttpRequest $request) + private function extractAccessToken(HttpRequest $request) { $headers = $request->getHeaders(); @@ -99,10 +98,26 @@ public function getAccessToken(HttpRequest $request) $token = $request->getQuery('access_token'); } - if (null === $token) { - return null; + return $token; + } + + /** + * Check if the given token is valid (not expired and/or match the given scopes) + * + * @param AccessToken $accessToken + * @param array $scopes + * @return bool + */ + private function isTokenValid(AccessToken $accessToken, $scopes = []) + { + if ($accessToken->isExpired()) { + return false; } - return $this->accessTokenService->getToken($token); + if (!empty($scopes) && !$accessToken->matchScopes($scopes)) { + return false; + } + + return true; } } diff --git a/tests/ZfrOAuth2Test/Server/ResourceServerTest.php b/tests/ZfrOAuth2Test/Server/ResourceServerTest.php index 4ea9ede..da08299 100644 --- a/tests/ZfrOAuth2Test/Server/ResourceServerTest.php +++ b/tests/ZfrOAuth2Test/Server/ResourceServerTest.php @@ -53,7 +53,8 @@ public function testCanExtractAccessTokenFromAuthorizationHeader() $request = new HttpRequest(); $request->getHeaders()->addHeaderLine('Authorization', 'Bearer token'); - $token = $this->getMock('ZfrOAuth2\Server\Entity\AbstractToken'); + $token = $this->getMock('ZfrOAuth2\Server\Entity\AccessToken'); + $token->expects($this->once())->method('isExpired')->will($this->returnValue(false)); $this->tokenService->expects($this->once()) ->method('getToken') @@ -68,7 +69,8 @@ public function testCanExtractAccessTokenFromQueryString() $request = new HttpRequest(); $request->getQuery()->fromArray(['access_token' => 'token']); - $token = $this->getMock('ZfrOAuth2\Server\Entity\AbstractToken'); + $token = $this->getMock('ZfrOAuth2\Server\Entity\AccessToken'); + $token->expects($this->once())->method('isExpired')->will($this->returnValue(false)); $this->tokenService->expects($this->once()) ->method('getToken') @@ -96,7 +98,7 @@ public function requestProvider() 'expired_token' => true, 'token_scope' => 'read', 'desired_scope' => 'read write', - 'result' => false + 'match' => false ], // Should return false because we are asking more permissions than the token scope @@ -104,7 +106,7 @@ public function requestProvider() 'expired_token' => false, 'token_scope' => 'read', 'desired_scope' => 'read write', - 'result' => false + 'match' => false ], // Should return true @@ -112,7 +114,7 @@ public function requestProvider() 'expired_token' => false, 'token_scope' => 'read', 'desired_scope' => 'read', - 'result' => true + 'match' => true ], ]; } @@ -120,7 +122,7 @@ public function requestProvider() /** * @dataProvider requestProvider */ - public function testCanValidateAccessToResource($expiredToken, $tokenScope, $desiredScope, $result) + public function testCanValidateAccessToResource($expiredToken, $tokenScope, $desiredScope, $match) { $request = new HttpRequest(); $request->getHeaders()->addHeaderLine('Authorization', 'Bearer token'); @@ -142,6 +144,12 @@ public function testCanValidateAccessToResource($expiredToken, $tokenScope, $des ->with('token') ->will($this->returnValue($accessToken)); - $this->assertEquals($result, $this->resourceServer->isRequestValid($request, $desiredScope)); + $tokenResult = $this->resourceServer->getAccessToken($request, $desiredScope); + + if ($match) { + $this->assertInstanceOf('ZfrOAuth2\Server\Entity\AccessToken', $tokenResult); + } else { + $this->assertNull($tokenResult); + } } }