diff --git a/composer.json b/composer.json index a15b39bf3..23b967bea 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "illuminate/contracts": "^5.2|^6|^7|^8", "illuminate/http": "^5.2|^6|^7|^8", "illuminate/support": "^5.2|^6|^7|^8", - "lcobucci/jwt": "<3.4", + "lcobucci/jwt": "^3.4", "namshi/jose": "^7.0", "nesbot/carbon": "^1.0|^2.0" }, diff --git a/src/Claims/DatetimeTrait.php b/src/Claims/DatetimeTrait.php index dbda1e3c9..783bd0c7a 100644 --- a/src/Claims/DatetimeTrait.php +++ b/src/Claims/DatetimeTrait.php @@ -12,7 +12,8 @@ namespace Tymon\JWTAuth\Claims; use DateInterval; -use DateTimeInterface; +use DateTime; +use DateTimeImmutable; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Support\Utils; @@ -40,8 +41,12 @@ public function setValue($value) $value = Utils::now()->add($value); } - if ($value instanceof DateTimeInterface) { - $value = $value->getTimestamp(); + if ($value instanceof DateTime) { + $value = DateTimeImmutable::createFromMutable($value); + } + + if (is_numeric($value)) { + $value = new DateTimeImmutable('@'.$value); } return parent::setValue($value); @@ -52,7 +57,7 @@ public function setValue($value) */ public function validateCreate($value) { - if (! is_numeric($value)) { + if (! is_a($value, DateTimeImmutable::class, true)) { throw new InvalidClaimException($this); } diff --git a/src/Claims/IssuedAt.php b/src/Claims/IssuedAt.php index 6253fe88d..219866f35 100644 --- a/src/Claims/IssuedAt.php +++ b/src/Claims/IssuedAt.php @@ -55,7 +55,7 @@ public function validatePayload() */ public function validateRefresh($refreshTTL) { - if ($this->isPast($this->getValue() + $refreshTTL * 60)) { + if ($this->isPast($this->getValue()->getTimestamp() + ($refreshTTL * 60))) { throw new TokenExpiredException('Token has expired and can no longer be refreshed'); } } diff --git a/src/Providers/JWT/Lcobucci.php b/src/Providers/JWT/Lcobucci.php index 126bdda30..98063ede0 100644 --- a/src/Providers/JWT/Lcobucci.php +++ b/src/Providers/JWT/Lcobucci.php @@ -22,6 +22,7 @@ use Lcobucci\JWT\Signer\Hmac\Sha256 as HS256; use Lcobucci\JWT\Signer\Hmac\Sha384 as HS384; use Lcobucci\JWT\Signer\Hmac\Sha512 as HS512; +use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Keychain; use Lcobucci\JWT\Signer\Rsa; use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256; @@ -105,15 +106,16 @@ public function encode(array $payload) $this->builder->unsign(); try { + $signingKey = $this->getSigningKey(); + $signingKey = is_string($signingKey) ? new Key($signingKey) : $signingKey; foreach ($payload as $key => $value) { $this->builder->set($key, $value); } - $this->builder->sign($this->signer, $this->getSigningKey()); + + return (string) $this->builder->getToken($this->signer, $signingKey); } catch (Exception $e) { throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e); } - - return (string) $this->builder->getToken(); } /** @@ -133,7 +135,10 @@ public function decode($token) throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e); } - if (! $jwt->verify($this->signer, $this->getVerificationKey())) { + $verificationKey = $this->getVerificationKey(); + $verificationKey = is_string($verificationKey) ? new Key($verificationKey) : $verificationKey; + + if (! $jwt->verify($this->signer, $verificationKey)) { throw new TokenInvalidException('Token Signature could not be verified.'); } diff --git a/src/Support/Utils.php b/src/Support/Utils.php index 5738e6ed7..6fc6104a3 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -28,19 +28,23 @@ public static function now() /** * Get the Carbon instance for the timestamp. * - * @param int $timestamp + * @param int|\DateTimeInterface $timestamp * * @return \Carbon\Carbon */ public static function timestamp($timestamp) { - return Carbon::createFromTimestampUTC($timestamp)->timezone('UTC'); + if (is_numeric($timestamp)) { + return Carbon::createFromTimestampUTC($timestamp)->timezone('UTC'); + } + + return Carbon::instance($timestamp)->timezone('UTC'); } /** * Checks if a timestamp is in the past. * - * @param int $timestamp + * @param int|\DateTimeInterface $timestamp * @param int $leeway * * @return bool @@ -57,7 +61,7 @@ public static function isPast($timestamp, $leeway = 0) /** * Checks if a timestamp is in the future. * - * @param int $timestamp + * @param int|\DateTimeInterface $instance * @param int $leeway * * @return bool diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 45011233b..0aeaff8c0 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -12,6 +12,7 @@ namespace Tymon\JWTAuth\Test; use Carbon\Carbon; +use Carbon\CarbonImmutable; use Mockery; use Yoast\PHPUnitPolyfills\TestCases\TestCase; @@ -22,12 +23,18 @@ abstract class AbstractTestCase extends TestCase */ protected $testNowTimestamp; + /** + * @var \Carbon\CarbonImmutable + */ + protected $testNowTimestampInstance; + public function setUp(): void { parent::setUp(); Carbon::setTestNow($now = Carbon::now()); $this->testNowTimestamp = $now->getTimestamp(); + $this->testNowTimestampInstance = CarbonImmutable::instance($now); } public function tearDown(): void diff --git a/tests/Claims/ClaimTest.php b/tests/Claims/ClaimTest.php index eed44cca4..dcca97c40 100644 --- a/tests/Claims/ClaimTest.php +++ b/tests/Claims/ClaimTest.php @@ -27,7 +27,7 @@ public function setUp(): void { parent::setUp(); - $this->claim = new Expiration($this->testNowTimestamp); + $this->claim = new Expiration($this->testNowTimestampInstance); } /** @test */ @@ -42,7 +42,7 @@ public function it_should_throw_an_exception_when_passing_an_invalid_value() /** @test */ public function it_should_convert_the_claim_to_an_array() { - $this->assertSame(['exp' => $this->testNowTimestamp], $this->claim->toArray()); + $this->assertSame(['exp' => $this->testNowTimestampInstance], $this->claim->toArray()); } /** @test */ diff --git a/tests/Claims/FactoryTest.php b/tests/Claims/FactoryTest.php index 057e1f61b..daae2fe74 100644 --- a/tests/Claims/FactoryTest.php +++ b/tests/Claims/FactoryTest.php @@ -78,11 +78,11 @@ public function it_should_get_a_custom_claim_instance_when_passing_a_non_defined public function it_should_make_a_claim_instance_with_a_value() { $iat = $this->factory->make('iat'); - $this->assertSame($iat->getValue(), $this->testNowTimestamp); + $this->assertSame($iat->getValue()->getTimestamp(), $this->testNowTimestampInstance->getTimestamp()); $this->assertInstanceOf(IssuedAt::class, $iat); $nbf = $this->factory->make('nbf'); - $this->assertSame($nbf->getValue(), $this->testNowTimestamp); + $this->assertSame($nbf->getValue()->getTimestamp(), $this->testNowTimestampInstance->getTimestamp()); $this->assertInstanceOf(NotBefore::class, $nbf); $iss = $this->factory->make('iss'); @@ -90,7 +90,10 @@ public function it_should_make_a_claim_instance_with_a_value() $this->assertInstanceOf(Issuer::class, $iss); $exp = $this->factory->make('exp'); - $this->assertSame($exp->getValue(), $this->testNowTimestamp + 3600); + $this->assertSame( + $exp->getValue()->getTimestamp(), + $this->testNowTimestampInstance->getTimestamp() + 3600 + ); $this->assertInstanceOf(Expiration::class, $exp); $jti = $this->factory->make('jti'); diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 773cbbe31..5b89f1d46 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -85,8 +85,8 @@ public function it_should_return_a_payload_when_passing_an_array_of_claims() $payload = $this->factory->claims(['sub' => 1, 'jti' => 'foo', 'iat' => 123, 'nbf' => 123])->make(); $this->assertSame($payload->get('sub'), 1); - $this->assertSame($payload->get('iat'), 123); - $this->assertSame($payload['exp'], $expTime); + $this->assertSame($payload->get('iat')->getTimestamp(), 123); + $this->assertSame($payload['exp']->getTimestamp(), $expTime); $this->assertSame($payload['jti'], 'foo'); $this->assertInstanceOf(Payload::class, $payload); diff --git a/tests/Providers/JWT/LcobucciTest.php b/tests/Providers/JWT/LcobucciTest.php index e3983023c..c4f69e571 100644 --- a/tests/Providers/JWT/LcobucciTest.php +++ b/tests/Providers/JWT/LcobucciTest.php @@ -11,11 +11,11 @@ namespace Tymon\JWTAuth\Test\Providers\JWT; -use Exception; use InvalidArgumentException; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Parser; use Lcobucci\JWT\Signer\Key; +use Lcobucci\JWT\Token; use Mockery; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; @@ -29,6 +29,11 @@ class LcobucciTest extends AbstractTestCase */ protected $parser; + /** + * @var \Mockery\MockInterface + */ + protected $token; + /** * @var \Mockery\MockInterface */ @@ -45,17 +50,19 @@ public function setUp(): void $this->builder = Mockery::mock(Builder::class); $this->parser = Mockery::mock(Parser::class); + $this->token = Mockery::mock(Token::class); } /** @test */ public function it_should_return_the_token_when_passing_a_valid_payload_to_encode() { $payload = ['sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo']; - + $signer = Mockery::any(); $this->builder->shouldReceive('unsign')->once()->andReturnSelf(); $this->builder->shouldReceive('set')->times(count($payload)); - $this->builder->shouldReceive('sign')->once()->with(Mockery::any(), 'secret'); - $this->builder->shouldReceive('getToken')->once()->andReturn('foo.bar.baz'); + $this->builder->shouldReceive('getToken')->once() + ->with(Mockery::any(), Mockery::type(Key::class)) + ->andReturn('foo.bar.baz'); $token = $this->getProvider('secret', 'HS256')->encode($payload); @@ -72,7 +79,6 @@ public function it_should_throw_an_invalid_exception_when_the_payload_could_not_ $this->builder->shouldReceive('unsign')->once()->andReturnSelf(); $this->builder->shouldReceive('set')->times(count($payload)); - $this->builder->shouldReceive('sign')->once()->with(Mockery::any(), 'secret')->andThrow(new Exception); $this->getProvider('secret', 'HS256')->encode($payload); } @@ -83,8 +89,10 @@ public function it_should_return_the_payload_when_passing_a_valid_token_to_decod $payload = ['sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo']; $this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn(Mockery::self()); - $this->parser->shouldReceive('verify')->once()->with(Mockery::any(), 'secret')->andReturn(true); - $this->parser->shouldReceive('getClaims')->once()->andReturn($payload); + $this->token->shouldReceive('verify')->once() + ->with(Mockery::any(), Mockery::type(Key::class)) + ->andReturn(true); + $this->token->shouldReceive('getClaims')->once()->andReturn($payload); $this->assertSame($payload, $this->getProvider('secret', 'HS256')->decode('foo.bar.baz')); } @@ -96,8 +104,10 @@ public function it_should_throw_a_token_invalid_exception_when_the_token_could_n $this->expectExceptionMessage('Token Signature could not be verified.'); $this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn(Mockery::self()); - $this->parser->shouldReceive('verify')->once()->with(Mockery::any(), 'secret')->andReturn(false); - $this->parser->shouldReceive('getClaims')->never(); + $this->token->shouldReceive('verify')->once() + ->with(Mockery::any(), Mockery::type(Key::class)) + ->andReturn(false); + $this->token->shouldReceive('getClaims')->never(); $this->getProvider('secret', 'HS256')->decode('foo.bar.baz'); } @@ -128,8 +138,9 @@ public function it_should_generate_a_token_when_using_an_rsa_algorithm() $this->builder->shouldReceive('unsign')->once()->andReturnSelf(); $this->builder->shouldReceive('set')->times(count($payload)); - $this->builder->shouldReceive('sign')->once()->with(Mockery::any(), Mockery::type(Key::class)); - $this->builder->shouldReceive('getToken')->once()->andReturn('foo.bar.baz'); + $this->builder->shouldReceive('getToken')->once() + ->with(Mockery::any(), Mockery::type(Key::class)) + ->andReturn('foo.bar.baz'); $token = $provider->encode($payload);