Unverified Commit aeae9e44 authored by Anner Visser's avatar Anner Visser
Browse files

Allow configuring leeway for the validation of jwt token dates

Fixes #1021
parent 0b32d7be
......@@ -14,8 +14,8 @@ use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use League\OAuth2\Server\CryptKey;
......@@ -43,12 +43,19 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
*/
private $jwtConfiguration;
/**
* @var \DateInterval|null
*/
private $jwtValidAtDateLeeway;
/**
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param \DateInterval|null $jwtValidAtDateLeeway
*/
public function __construct(AccessTokenRepositoryInterface $accessTokenRepository)
public function __construct(AccessTokenRepositoryInterface $accessTokenRepository, \DateInterval $jwtValidAtDateLeeway = null)
{
$this->accessTokenRepository = $accessTokenRepository;
$this->jwtValidAtDateLeeway = $jwtValidAtDateLeeway;
}
/**
......@@ -73,10 +80,11 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
InMemory::plainText('empty', 'empty')
);
$clock = new SystemClock(new DateTimeZone(\date_default_timezone_get()));
$this->jwtConfiguration->setValidationConstraints(
\class_exists(LooseValidAt::class)
? new LooseValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get())))
: new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))),
? new LooseValidAt($clock, $this->jwtValidAtDateLeeway)
: new ValidAt($clock, $this->jwtValidAtDateLeeway),
new SignedWith(
new Sha256(),
InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '')
......
......@@ -71,4 +71,67 @@ class BearerTokenValidatorTest extends TestCase
$bearerTokenValidator->validateAuthorization($request);
}
public function testBearerTokenValidatorAcceptsExpiredTokenWithinLeeway()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
// We fake generating this token 10 seconds into the future, an extreme example of possible time drift between servers
$future = (new DateTimeImmutable())->add(new DateInterval('PT10S'));
$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock, new \DateInterval('PT10S'));
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class);
$jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration');
$jwtConfiguration->setAccessible(true);
$jwtTokenFromFutureWithinLeeway = $jwtConfiguration->getValue($bearerTokenValidator)->builder()
->permittedFor('client-id')
->identifiedBy('token-id')
->issuedAt($future)
->canOnlyBeUsedAfter($future)
->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H')))
->relatedTo('user-id')
->withClaim('scopes', 'scope1 scope2 scope3 scope4')
->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key'));
$request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $jwtTokenFromFutureWithinLeeway->toString()));
$validRequest = $bearerTokenValidator->validateAuthorization($request);
$this->assertArrayHasKey('authorization', $validRequest->getHeaders());
}
public function testBearerTokenValidatorRejectsExpiredTokenBeyondLeeway()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
// We fake generating this token 10 seconds into the future, an extreme example of possible time drift between servers
$future = (new DateTimeImmutable())->add(new DateInterval('PT20S'));
$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock, new \DateInterval('PT10S'));
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class);
$jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration');
$jwtConfiguration->setAccessible(true);
$jwtTokenFromFutureBeyondLeeway = $jwtConfiguration->getValue($bearerTokenValidator)->builder()
->permittedFor('client-id')
->identifiedBy('token-id')
->issuedAt($future)
->canOnlyBeUsedAfter($future)
->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H')))
->relatedTo('user-id')
->withClaim('scopes', 'scope1 scope2 scope3 scope4')
->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key'));
$request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $jwtTokenFromFutureBeyondLeeway->toString()));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(9);
$bearerTokenValidator->validateAuthorization($request);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment