mirror of
https://github.com/elyby/oauth2-server.git
synced 2024-11-30 10:42:02 +05:30
Implemented RFC7636. Fixes #574
This commit is contained in:
parent
4a4f4fe2d7
commit
8e8aed1a50
@ -172,6 +172,26 @@ class OAuthServerException extends \Exception
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid grant.
|
||||||
|
*
|
||||||
|
* @param string $hint
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function invalidGrant($hint = '')
|
||||||
|
{
|
||||||
|
return new static(
|
||||||
|
'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
|
||||||
|
. 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
|
||||||
|
. 'or was issued to another client.',
|
||||||
|
10,
|
||||||
|
'invalid_grant',
|
||||||
|
400,
|
||||||
|
$hint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -29,6 +29,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
*/
|
*/
|
||||||
private $authCodeTTL;
|
private $authCodeTTL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $enableCodeExchangeProof = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository
|
* @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository
|
||||||
* @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
|
* @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
|
||||||
@ -45,6 +50,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
$this->refreshTokenTTL = new \DateInterval('P1M');
|
$this->refreshTokenTTL = new \DateInterval('P1M');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function enableCodeExchangeProof()
|
||||||
|
{
|
||||||
|
$this->enableCodeExchangeProof = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Respond to an access token request.
|
* Respond to an access token request.
|
||||||
*
|
*
|
||||||
@ -118,6 +128,42 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
|
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate code challenge
|
||||||
|
if ($this->enableCodeExchangeProof === true) {
|
||||||
|
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
|
||||||
|
if ($codeVerifier === null) {
|
||||||
|
throw OAuthServerException::invalidRequest('code_verifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($authCodePayload->code_challenge_method) {
|
||||||
|
case 'plain':
|
||||||
|
if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) {
|
||||||
|
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'S256':
|
||||||
|
if (
|
||||||
|
hash_equals(
|
||||||
|
urlencode(base64_encode(hash('sha256', $codeVerifier))),
|
||||||
|
$authCodePayload->code_challenge
|
||||||
|
) === false
|
||||||
|
) {
|
||||||
|
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
|
||||||
|
}
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw OAuthServerException::serverError(
|
||||||
|
sprintf(
|
||||||
|
'Unsupported code challenge method `%s`',
|
||||||
|
$authCodePayload->code_challenge_method
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Issue and persist access + refresh tokens
|
// Issue and persist access + refresh tokens
|
||||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
|
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
|
||||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||||
@ -213,6 +259,24 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
$authorizationRequest->setState($stateParameter);
|
$authorizationRequest->setState($stateParameter);
|
||||||
$authorizationRequest->setScopes($scopes);
|
$authorizationRequest->setScopes($scopes);
|
||||||
|
|
||||||
|
if ($this->enableCodeExchangeProof === true) {
|
||||||
|
$codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
|
||||||
|
if ($codeChallenge === null) {
|
||||||
|
throw OAuthServerException::invalidRequest('code_challenge');
|
||||||
|
}
|
||||||
|
|
||||||
|
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
|
||||||
|
if (in_array($codeChallengeMethod, ['plain', 'S256']) === false) {
|
||||||
|
throw OAuthServerException::invalidRequest(
|
||||||
|
'code_challenge_method',
|
||||||
|
'Code challenge method must be `plain` or `S256`'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorizationRequest->setCodeChallenge($codeChallenge);
|
||||||
|
$authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
|
||||||
|
}
|
||||||
|
|
||||||
return $authorizationRequest;
|
return $authorizationRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,12 +313,14 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
'code' => $this->encrypt(
|
'code' => $this->encrypt(
|
||||||
json_encode(
|
json_encode(
|
||||||
[
|
[
|
||||||
'client_id' => $authCode->getClient()->getIdentifier(),
|
'client_id' => $authCode->getClient()->getIdentifier(),
|
||||||
'redirect_uri' => $authCode->getRedirectUri(),
|
'redirect_uri' => $authCode->getRedirectUri(),
|
||||||
'auth_code_id' => $authCode->getIdentifier(),
|
'auth_code_id' => $authCode->getIdentifier(),
|
||||||
'scopes' => $authCode->getScopes(),
|
'scopes' => $authCode->getScopes(),
|
||||||
'user_id' => $authCode->getUserIdentifier(),
|
'user_id' => $authCode->getUserIdentifier(),
|
||||||
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
|
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
|
||||||
|
'code_challenge' => $authorizationRequest->getCodeChallenge(),
|
||||||
|
'code_challenge_method ' => $authorizationRequest->getCodeChallengeMethod(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -64,6 +64,18 @@ class AuthorizationRequest
|
|||||||
*/
|
*/
|
||||||
protected $state;
|
protected $state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The code challenge (if provided)
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $codeChallenge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The code challenge method (if provided)
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $codeChallengeMethod;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@ -175,4 +187,36 @@ class AuthorizationRequest
|
|||||||
{
|
{
|
||||||
$this->state = $state;
|
$this->state = $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCodeChallenge()
|
||||||
|
{
|
||||||
|
return $this->codeChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $codeChallenge
|
||||||
|
*/
|
||||||
|
public function setCodeChallenge($codeChallenge)
|
||||||
|
{
|
||||||
|
$this->codeChallenge = $codeChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCodeChallengeMethod()
|
||||||
|
{
|
||||||
|
return $this->codeChallengeMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $codeChallengeMethod
|
||||||
|
*/
|
||||||
|
public function setCodeChallengeMethod($codeChallengeMethod)
|
||||||
|
{
|
||||||
|
$this->codeChallengeMethod = $codeChallengeMethod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ use Zend\Diactoros\ServerRequest;
|
|||||||
class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* CryptTrait stub
|
* @var CryptTraitStub
|
||||||
*/
|
*/
|
||||||
protected $cryptStub;
|
protected $cryptStub;
|
||||||
|
|
||||||
@ -136,6 +136,41 @@ class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertTrue($grant->validateAuthorizationRequest($request) instanceof AuthorizationRequest);
|
$this->assertTrue($grant->validateAuthorizationRequest($request) instanceof AuthorizationRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testValidateAuthorizationRequestCodeChallenge()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'php://input',
|
||||||
|
$headers = [],
|
||||||
|
$cookies = [],
|
||||||
|
$queryParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'FOOBAR',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue($grant->validateAuthorizationRequest($request) instanceof AuthorizationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
||||||
* @expectedExceptionCode 3
|
* @expectedExceptionCode 3
|
||||||
@ -272,6 +307,82 @@ class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
|||||||
$grant->validateAuthorizationRequest($request);
|
$grant->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
||||||
|
* @expectedExceptionCode 3
|
||||||
|
*/
|
||||||
|
public function testValidateAuthorizationRequestMissingCodeChallenge()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'php://input',
|
||||||
|
$headers = [],
|
||||||
|
$cookies = [],
|
||||||
|
$queryParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$grant->validateAuthorizationRequest($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
||||||
|
* @expectedExceptionCode 3
|
||||||
|
*/
|
||||||
|
public function testValidateAuthorizationRequestInvalidCodeChallengeMethod()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'php://input',
|
||||||
|
$headers = [],
|
||||||
|
$cookies = [],
|
||||||
|
$queryParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'foobar',
|
||||||
|
'code_challenge_method' => 'foo',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$grant->validateAuthorizationRequest($request);
|
||||||
|
}
|
||||||
|
|
||||||
public function testCompleteAuthorizationRequest()
|
public function testCompleteAuthorizationRequest()
|
||||||
{
|
{
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
@ -390,6 +501,150 @@ class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertTrue($response->getRefreshToken() instanceof RefreshTokenEntityInterface);
|
$this->assertTrue($response->getRefreshToken() instanceof RefreshTokenEntityInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRespondToAccessTokenRequestCodeChallengePlain()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('foo');
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeEntity = new ScopeEntity();
|
||||||
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
||||||
|
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
|
$grant->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
'POST',
|
||||||
|
'php://input',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_verifier' => 'foobar',
|
||||||
|
'code' => $this->cryptStub->doEncrypt(
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'auth_code_id' => uniqid(),
|
||||||
|
'expire_time' => time() + 3600,
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'user_id' => 123,
|
||||||
|
'scopes' => ['foo'],
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'foobar',
|
||||||
|
'code_challenge_method' => 'plain',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var StubResponseType $response */
|
||||||
|
$response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
|
||||||
|
|
||||||
|
$this->assertTrue($response->getAccessToken() instanceof AccessTokenEntityInterface);
|
||||||
|
$this->assertTrue($response->getRefreshToken() instanceof RefreshTokenEntityInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRespondToAccessTokenRequestCodeChallengeS256()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('foo');
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeEntity = new ScopeEntity();
|
||||||
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
||||||
|
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
|
$grant->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
'POST',
|
||||||
|
'php://input',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_verifier' => 'foobar',
|
||||||
|
'code' => $this->cryptStub->doEncrypt(
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'auth_code_id' => uniqid(),
|
||||||
|
'expire_time' => time() + 3600,
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'user_id' => 123,
|
||||||
|
'scopes' => ['foo'],
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => urlencode(base64_encode(hash('sha256', 'foobar'))),
|
||||||
|
'code_challenge_method' => 'S256',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var StubResponseType $response */
|
||||||
|
$response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
|
||||||
|
|
||||||
|
$this->assertTrue($response->getAccessToken() instanceof AccessTokenEntityInterface);
|
||||||
|
$this->assertTrue($response->getRefreshToken() instanceof RefreshTokenEntityInterface);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
||||||
* @expectedExceptionCode 3
|
* @expectedExceptionCode 3
|
||||||
@ -710,4 +965,222 @@ class AuthCodeGrantTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($e->getHint(), 'Cannot decrypt the authorization code');
|
$this->assertEquals($e->getHint(), 'Cannot decrypt the authorization code');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public function testRespondToAccessTokenRequestBadCodeVerifierPlain()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('foo');
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeEntity = new ScopeEntity();
|
||||||
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
||||||
|
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
$grant->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
'POST',
|
||||||
|
'php://input',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_verifier' => 'nope',
|
||||||
|
'code' => $this->cryptStub->doEncrypt(
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'auth_code_id' => uniqid(),
|
||||||
|
'expire_time' => time() + 3600,
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'user_id' => 123,
|
||||||
|
'scopes' => ['foo'],
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'foobar',
|
||||||
|
'code_challenge_method' => 'plain',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* @var StubResponseType $response */
|
||||||
|
$grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
$this->assertEquals($e->getHint(), 'Failed to verify `code_verifier`.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRespondToAccessTokenRequestBadCodeVerifierS256()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('foo');
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeEntity = new ScopeEntity();
|
||||||
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
||||||
|
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
$grant->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
'POST',
|
||||||
|
'php://input',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_verifier' => 'nope',
|
||||||
|
'code' => $this->cryptStub->doEncrypt(
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'auth_code_id' => uniqid(),
|
||||||
|
'expire_time' => time() + 3600,
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'user_id' => 123,
|
||||||
|
'scopes' => ['foo'],
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'foobar',
|
||||||
|
'code_challenge_method' => 'S256',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* @var StubResponseType $response */
|
||||||
|
$grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
$this->assertEquals($e->getHint(), 'Failed to verify `code_verifier`.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRespondToAccessTokenRequestMissingCodeVerifier()
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('foo');
|
||||||
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeEntity = new ScopeEntity();
|
||||||
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
||||||
|
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
||||||
|
|
||||||
|
$grant = new AuthCodeGrant(
|
||||||
|
$this->getMock(AuthCodeRepositoryInterface::class),
|
||||||
|
$this->getMock(RefreshTokenRepositoryInterface::class),
|
||||||
|
new \DateInterval('PT10M')
|
||||||
|
);
|
||||||
|
$grant->enableCodeExchangeProof();
|
||||||
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
$grant->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$request = new ServerRequest(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
'POST',
|
||||||
|
'php://input',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code' => $this->cryptStub->doEncrypt(
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'auth_code_id' => uniqid(),
|
||||||
|
'expire_time' => time() + 3600,
|
||||||
|
'client_id' => 'foo',
|
||||||
|
'user_id' => 123,
|
||||||
|
'scopes' => ['foo'],
|
||||||
|
'redirect_uri' => 'http://foo/bar',
|
||||||
|
'code_challenge' => 'foobar',
|
||||||
|
'code_challenge_method' => 'plain',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* @var StubResponseType $response */
|
||||||
|
$grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
$this->assertEquals($e->getHint(), 'Check the `code_verifier` parameter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user