Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 43e62cd

Browse files
committedSep 21, 2021
Migrated pull request tymondesigns/jwt-auth#2117 to update lcobucci to version 4 and php 8
1 parent 29c8c69 commit 43e62cd

File tree

4 files changed

+215
-77
lines changed

4 files changed

+215
-77
lines changed
 

‎composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"illuminate/database": "^8.61",
3535
"illuminate/http": "^5.2|^6|^7|^8",
3636
"illuminate/support": "^5.2|^6|^7|^8",
37-
"lcobucci/jwt": "<3.4",
37+
"lcobucci/jwt": "^4.0",
3838
"namshi/jose": "^7.0",
3939
"nesbot/carbon": "^1.0|^2.0"
4040
},

‎src/Providers/AbstractServiceProvider.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
namespace PHPOpenSourceSaver\JWTAuth\Providers;
1313

1414
use Illuminate\Support\ServiceProvider;
15-
use Lcobucci\JWT\Builder as JWTBuilder;
16-
use Lcobucci\JWT\Parser as JWTParser;
1715
use Namshi\JOSE\JWS;
1816
use PHPOpenSourceSaver\JWTAuth\Blacklist;
1917
use PHPOpenSourceSaver\JWTAuth\Claims\Factory as ClaimFactory;
@@ -167,8 +165,6 @@ protected function registerLcobucciProvider()
167165
{
168166
$this->app->singleton('tymon.jwt.provider.jwt.lcobucci', function ($app) {
169167
return new Lcobucci(
170-
new JWTBuilder(),
171-
new JWTParser(),
172168
$this->config('secret'),
173169
$this->config('algo'),
174170
$this->config('keys')

‎src/Providers/JWT/Lcobucci.php

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@
1414
use Exception;
1515
use Illuminate\Support\Collection;
1616
use Lcobucci\JWT\Builder;
17-
use Lcobucci\JWT\Parser;
17+
use Lcobucci\JWT\Configuration;
1818
use Lcobucci\JWT\Signer\Ecdsa;
1919
use Lcobucci\JWT\Signer\Ecdsa\Sha256 as ES256;
2020
use Lcobucci\JWT\Signer\Ecdsa\Sha384 as ES384;
2121
use Lcobucci\JWT\Signer\Ecdsa\Sha512 as ES512;
2222
use Lcobucci\JWT\Signer\Hmac\Sha256 as HS256;
2323
use Lcobucci\JWT\Signer\Hmac\Sha384 as HS384;
2424
use Lcobucci\JWT\Signer\Hmac\Sha512 as HS512;
25-
use Lcobucci\JWT\Signer\Keychain;
25+
use Lcobucci\JWT\Signer\Key\InMemory;
26+
use Lcobucci\JWT\Signer;
27+
use Lcobucci\JWT\Signer\Key;
2628
use Lcobucci\JWT\Signer\Rsa;
2729
use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256;
2830
use Lcobucci\JWT\Signer\Rsa\Sha384 as RS384;
2931
use Lcobucci\JWT\Signer\Rsa\Sha512 as RS512;
32+
use Lcobucci\JWT\Token\RegisteredClaims;
33+
use Lcobucci\JWT\Validation\Constraint\SignedWith;
3034
use ReflectionClass;
3135
use PHPOpenSourceSaver\JWTAuth\Contracts\Providers\JWT;
3236
use PHPOpenSourceSaver\JWTAuth\Exceptions\JWTException;
@@ -35,42 +39,68 @@
3539
class Lcobucci extends Provider implements JWT
3640
{
3741
/**
38-
* The Builder instance.
42+
* The builder instance.
3943
*
40-
* @var \Lcobucci\JWT\Builder
44+
* @var Builder
4145
*/
4246
protected $builder;
4347

4448
/**
45-
* The Parser instance.
49+
* The configuration instance.
4650
*
47-
* @var \Lcobucci\JWT\Parser
51+
* @var Configuration
4852
*/
49-
protected $parser;
53+
protected $config;
54+
55+
/**
56+
* The Signer instance.
57+
*
58+
* @var Signer
59+
*/
60+
protected $signer;
5061

5162
/**
5263
* Create the Lcobucci provider.
5364
*
54-
* @param \Lcobucci\JWT\Builder $builder
55-
* @param \Lcobucci\JWT\Parser $parser
5665
* @param string $secret
5766
* @param string $algo
5867
* @param array $keys
68+
* @param Configuration $config Optional, to pass an existing configuration to be used.
5969
*
6070
* @return void
6171
*/
6272
public function __construct(
63-
Builder $builder,
64-
Parser $parser,
6573
$secret,
6674
$algo,
67-
array $keys
75+
array $keys,
76+
$config = null
6877
) {
6978
parent::__construct($secret, $algo, $keys);
7079

71-
$this->builder = $builder;
72-
$this->parser = $parser;
7380
$this->signer = $this->getSigner();
81+
82+
if (!is_null($config)) {
83+
$this->config = $config;
84+
} elseif ($this->isAsymmetric()) {
85+
$this->config = Configuration::forAsymmetricSigner($this->signer, $this->getSigningKey(), $this->getVerificationKey());
86+
} else {
87+
$this->config = Configuration::forSymmetricSigner($this->signer, InMemory::plainText($this->getSecret()));
88+
}
89+
if (!count($this->config->validationConstraints())) {
90+
$this->config->setValidationConstraints(
91+
new SignedWith($this->signer, $this->getVerificationKey()),
92+
);
93+
}
94+
}
95+
96+
/**
97+
* Gets the {@see $config} attribute.
98+
*
99+
* @return Configuration
100+
*/
101+
public function getConfig()
102+
{
103+
return $this->config;
74104
}
75105

76106
/**
@@ -101,19 +131,18 @@ public function __construct(
101131
*/
102132
public function encode(array $payload)
103133
{
104-
// Remove the signature on the builder instance first.
105-
$this->builder->unsign();
134+
$this->builder = null;
135+
$this->builder = $this->config->builder();
106136

107137
try {
108138
foreach ($payload as $key => $value) {
109-
$this->builder->set($key, $value);
139+
$this->addClaim($key, $value);
110140
}
111-
$this->builder->sign($this->signer, $this->getSigningKey());
141+
142+
return $this->builder->getToken($this->config->signer(), $this->config->signingKey())->toString();
112143
} catch (Exception $e) {
113-
throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
144+
throw new JWTException('Could not create token: ' . $e->getMessage(), $e->getCode(), $e);
114145
}
115-
116-
return (string) $this->builder->getToken();
117146
}
118147

119148
/**
@@ -128,20 +157,66 @@ public function encode(array $payload)
128157
public function decode($token)
129158
{
130159
try {
131-
$jwt = $this->parser->parse($token);
160+
$jwt = $this->config->parser()->parse($token);
132161
} catch (Exception $e) {
133-
throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e);
162+
throw new TokenInvalidException('Could not decode token: ' . $e->getMessage(), $e->getCode(), $e);
134163
}
135164

136-
if (! $jwt->verify($this->signer, $this->getVerificationKey())) {
165+
if (!$this->config->validator()->validate($jwt, ...$this->config->validationConstraints())) {
137166
throw new TokenInvalidException('Token Signature could not be verified.');
138167
}
139168

140-
return (new Collection($jwt->getClaims()))->map(function ($claim) {
141-
return is_object($claim) ? $claim->getValue() : $claim;
169+
return (new Collection($jwt->claims()->all()))->map(function ($claim) {
170+
if (is_a($claim, \DateTimeImmutable::class)) {
171+
return $claim->getTimestamp();
172+
}
173+
if (is_object($claim) && method_exists($claim, 'getValue')) {
174+
return $claim->getValue();
175+
}
176+
177+
return $claim;
142178
})->toArray();
143179
}
144180

181+
/**
182+
* Adds a claim to the {@see $config}.
183+
*
184+
* @param string $key
185+
* @param mixed $value
186+
*/
187+
protected function addClaim($key, $value)
188+
{
189+
if (!isset($this->builder)) {
190+
$this->builder = $this->config->builder();
191+
}
192+
193+
switch ($key) {
194+
case RegisteredClaims::ID:
195+
$this->builder->identifiedBy($value);
196+
break;
197+
case RegisteredClaims::EXPIRATION_TIME:
198+
$this->builder->expiresAt(\DateTimeImmutable::createFromFormat('U', $value));
199+
break;
200+
case RegisteredClaims::NOT_BEFORE:
201+
$this->builder->canOnlyBeUsedAfter(\DateTimeImmutable::createFromFormat('U', $value));
202+
break;
203+
case RegisteredClaims::ISSUED_AT:
204+
$this->builder->issuedAt(\DateTimeImmutable::createFromFormat('U', $value));
205+
break;
206+
case RegisteredClaims::ISSUER:
207+
$this->builder->issuedBy($value);
208+
break;
209+
case RegisteredClaims::AUDIENCE:
210+
$this->builder->permittedFor($value);
211+
break;
212+
case RegisteredClaims::SUBJECT:
213+
$this->builder->relatedTo($value);
214+
break;
215+
default:
216+
$this->builder->withClaim($key, $value);
217+
}
218+
}
219+
145220
/**
146221
* Get the signer instance.
147222
*
@@ -151,7 +226,7 @@ public function decode($token)
151226
*/
152227
protected function getSigner()
153228
{
154-
if (! array_key_exists($this->algo, $this->signers)) {
229+
if (!array_key_exists($this->algo, $this->signers)) {
155230
throw new JWTException('The given algorithm could not be found');
156231
}
157232

@@ -169,22 +244,26 @@ protected function isAsymmetric()
169244
}
170245

171246
/**
172-
* {@inheritdoc}
247+
* Get the key used to sign the tokens.
248+
*
249+
* @return Key|string
173250
*/
174251
protected function getSigningKey()
175252
{
176253
return $this->isAsymmetric() ?
177-
(new Keychain())->getPrivateKey($this->getPrivateKey(), $this->getPassphrase()) :
178-
$this->getSecret();
254+
InMemory::plainText($this->getPrivateKey(), $this->getPassphrase() ?? '') :
255+
InMemory::plainText($this->getSecret());
179256
}
180257

181258
/**
182-
* {@inheritdoc}
259+
* Get the key used to verify the tokens.
260+
*
261+
* @return Key|string
183262
*/
184263
protected function getVerificationKey()
185264
{
186265
return $this->isAsymmetric() ?
187-
(new Keychain())->getPublicKey($this->getPublicKey()) :
188-
$this->getSecret();
266+
InMemory::plainText($this->getPublicKey()) :
267+
InMemory::plainText($this->getSecret());
189268
}
190269
}

‎tests/Providers/JWT/LcobucciTest.php

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
use InvalidArgumentException;
1616
use Lcobucci\JWT\Builder;
1717
use Lcobucci\JWT\Parser;
18+
use Lcobucci\JWT\Signer;
1819
use Lcobucci\JWT\Signer\Key;
20+
use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256;
21+
use Lcobucci\JWT\Token;
22+
use Lcobucci\JWT\Token\DataSet;
23+
use Lcobucci\JWT\Validation\Constraint;
1924
use Mockery;
2025
use Mockery\MockInterface;
2126
use PHPOpenSourceSaver\JWTAuth\Exceptions\JWTException;
@@ -26,6 +31,13 @@
2631

2732
class LcobucciTest extends AbstractTestCase
2833
{
34+
/**
35+
* Mocks {@see Configuration}.
36+
*
37+
* @var MockInterface
38+
*/
39+
protected $config;
40+
2941
/**
3042
* @var MockInterface
3143
*/
@@ -37,9 +49,9 @@ class LcobucciTest extends AbstractTestCase
3749
protected $builder;
3850

3951
/**
40-
* @var Namshi
52+
* @var MockInterface
4153
*/
42-
protected $provider;
54+
protected $validator;
4355

4456
public function setUp(): void
4557
{
@@ -54,19 +66,22 @@ public function it_should_return_the_token_when_passing_a_valid_payload_to_encod
5466
{
5567
$payload = ['sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo'];
5668

57-
$this->builder->shouldReceive('unsign')->once()->andReturnSelf();
58-
$this->builder->shouldReceive('set')->times(count($payload));
59-
$this->builder->shouldReceive('sign')->once()->with(Mockery::any(), 'secret');
60-
$this->builder->shouldReceive('getToken')->once()->andReturn('foo.bar.baz');
69+
$dataSet = new DataSet($payload, 'payload');
6170

62-
$token = $this->getProvider('secret', 'HS256')->encode($payload);
71+
$this->builder->shouldReceive('relatedTo')->once()->andReturnSelf(); // sub
72+
$this->builder->shouldReceive('expiresAt')->once()->andReturnSelf(); // exp
73+
$this->builder->shouldReceive('issuedAt')->once()->andReturnSelf(); // iat
74+
$this->builder->shouldReceive('issuedBy')->once()->andReturnSelf(); // iss
75+
$this->builder
76+
->shouldReceive('getToken')
77+
->once()
78+
->with(\Mockery::type(Signer::class), \Mockery::type(Key::class))
79+
->andReturn(new Token\Plain(new DataSet([], 'header'), $dataSet, (new Token\Signature('', 'signature'))));
6380

64-
$this->assertSame('foo.bar.baz', $token);
65-
}
81+
/** @var Token $token */
82+
$token = $this->getProvider('secret', 'HS256')->encode($payload);
6683

67-
public function getProvider($secret, $algo, array $keys = [])
68-
{
69-
return new Lcobucci($this->builder, $this->parser, $secret, $algo, $keys);
84+
$this->assertSame('header.payload.signature', $token);
7085
}
7186

7287
/** @test */
@@ -77,9 +92,15 @@ public function it_should_throw_an_invalid_exception_when_the_payload_could_not_
7792

7893
$payload = ['sub' => 1, 'exp' => $this->testNowTimestamp, 'iat' => $this->testNowTimestamp, 'iss' => '/foo'];
7994

80-
$this->builder->shouldReceive('unsign')->once()->andReturnSelf();
81-
$this->builder->shouldReceive('set')->times(count($payload));
82-
$this->builder->shouldReceive('sign')->once()->with(Mockery::any(), 'secret')->andThrow(new Exception);
95+
$this->builder->shouldReceive('relatedTo')->once()->andReturnSelf(); // sub
96+
$this->builder->shouldReceive('expiresAt')->once()->andReturnSelf(); // exp
97+
$this->builder->shouldReceive('issuedAt')->once()->andReturnSelf(); // iat
98+
$this->builder->shouldReceive('issuedBy')->once()->andReturnSelf(); // iss
99+
$this->builder
100+
->shouldReceive('getToken')
101+
->once()
102+
->with(\Mockery::type(Signer::class), \Mockery::type(Key::class))
103+
->andThrow(new Exception);
83104

84105
$this->getProvider('secret', 'HS256')->encode($payload);
85106
}
@@ -89,24 +110,36 @@ public function it_should_return_the_payload_when_passing_a_valid_token_to_decod
89110
{
90111
$payload = ['sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo'];
91112

92-
$this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn(Mockery::self());
93-
$this->parser->shouldReceive('verify')->once()->with(Mockery::any(), 'secret')->andReturn(true);
94-
$this->parser->shouldReceive('getClaims')->once()->andReturn($payload);
113+
$token = Mockery::mock(Token::class);
114+
$dataSet = Mockery::mock(new DataSet($payload, 'payload'));
115+
116+
$provider = $this->getProvider('secret', 'HS256');
117+
118+
$this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn($token);
119+
$this->validator->shouldReceive('validate')->once()->with($token, Mockery::any())->andReturnTrue();
120+
$token->shouldReceive('claims')->once()->andReturn($dataSet);
121+
$dataSet->shouldReceive('all')->once()->andReturn($payload);
95122

96-
$this->assertSame($payload, $this->getProvider('secret', 'HS256')->decode('foo.bar.baz'));
123+
$this->assertSame($payload, $provider->decode('foo.bar.baz'));
97124
}
98125

99126
/** @test */
100127
public function it_should_throw_a_token_invalid_exception_when_the_token_could_not_be_decoded_due_to_a_bad_signature()
101128
{
129+
$token = Mockery::mock(Token::class);
130+
$dataSet = Mockery::mock(new DataSet(['pay', 'load'], 'payload'));
131+
132+
$provider = $this->getProvider('secret', 'HS256');
133+
102134
$this->expectException(TokenInvalidException::class);
103135
$this->expectExceptionMessage('Token Signature could not be verified.');
104136

105-
$this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn(Mockery::self());
106-
$this->parser->shouldReceive('verify')->once()->with(Mockery::any(), 'secret')->andReturn(false);
107-
$this->parser->shouldReceive('getClaims')->never();
137+
$this->parser->shouldReceive('parse')->once()->with('foo.bar.baz')->andReturn($token);
138+
$this->validator->shouldReceive('validate')->once()->with($token, Mockery::any())->andReturnFalse();
139+
$token->shouldReceive('claims')->never();
140+
$dataSet->shouldReceive('all')->never();
108141

109-
$this->getProvider('secret', 'HS256')->decode('foo.bar.baz');
142+
$provider->decode('foo.bar.baz');
110143
}
111144

112145
/** @test */
@@ -125,32 +158,32 @@ public function it_should_throw_a_token_invalid_exception_when_the_token_could_n
125158
/** @test */
126159
public function it_should_generate_a_token_when_using_an_rsa_algorithm()
127160
{
161+
$dummyPrivateKey = $this->getDummyPrivateKey();
162+
$dummyPublicKey = $this->getDummyPublicKey();
163+
128164
$provider = $this->getProvider(
129165
'does_not_matter',
130166
'RS256',
131-
['private' => $this->getDummyPrivateKey(), 'public' => $this->getDummyPublicKey()]
167+
['private' => $dummyPrivateKey, 'public' => $dummyPublicKey]
132168
);
133169

134170
$payload = ['sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo'];
135171

136-
$this->builder->shouldReceive('unsign')->once()->andReturnSelf();
137-
$this->builder->shouldReceive('set')->times(count($payload));
138-
$this->builder->shouldReceive('sign')->once()->with(Mockery::any(), Mockery::type(Key::class));
139-
$this->builder->shouldReceive('getToken')->once()->andReturn('foo.bar.baz');
140-
141-
$token = $provider->encode($payload);
172+
$dataSet = new DataSet($payload, 'payload');
142173

143-
$this->assertSame('foo.bar.baz', $token);
144-
}
174+
$this->builder->shouldReceive('relatedTo')->once()->andReturnSelf(); // sub
175+
$this->builder->shouldReceive('expiresAt')->once()->andReturnSelf(); // exp
176+
$this->builder->shouldReceive('issuedAt')->once()->andReturnSelf(); // iat
177+
$this->builder->shouldReceive('issuedBy')->once()->andReturnSelf(); // iss
178+
$this->builder
179+
->shouldReceive('getToken')
180+
->once()
181+
->with(Mockery::type(RS256::class), Mockery::type(Key::class))
182+
->andReturn(new Token\Plain(new DataSet([], 'header'), $dataSet, (new Token\Signature('', 'signature'))));
145183

146-
public function getDummyPrivateKey()
147-
{
148-
return file_get_contents(__DIR__ . '/../Keys/id_rsa');
149-
}
184+
$token = $provider->encode($payload);
150185

151-
public function getDummyPublicKey()
152-
{
153-
return file_get_contents(__DIR__ . '/../Keys/id_rsa.pub');
186+
$this->assertSame('header.payload.signature', $token);
154187
}
155188

156189
/** @test */
@@ -188,4 +221,34 @@ public function it_should_return_the_keys()
188221

189222
$this->assertSame($keys, $provider->getKeys());
190223
}
224+
225+
public function getProvider($secret, $algo, array $keys = [])
226+
{
227+
$provider = new Lcobucci($secret, $algo, $keys);
228+
229+
$this->validator = Mockery::mock(\Lcobucci\JWT\Validator::class);
230+
$this->config = Mockery::mock($provider->getConfig());
231+
232+
$provider = new Lcobucci($secret, $algo, $keys, $this->config);
233+
234+
$this->config->shouldReceive('builder')->andReturn($this->builder);
235+
$this->config->shouldReceive('parser')->andReturn($this->parser);
236+
$this->config->shouldReceive('validator')->andReturn($this->validator);
237+
238+
$constraint = Mockery::mock(Constraint::class);
239+
$constraint->shouldReceive('assert')->andReturn();
240+
$this->config->shouldReceive('validationConstraints')->andReturn([$constraint]);
241+
242+
return $provider;
243+
}
244+
245+
public function getDummyPrivateKey()
246+
{
247+
return file_get_contents(__DIR__ . '/../Keys/id_rsa');
248+
}
249+
250+
public function getDummyPublicKey()
251+
{
252+
return file_get_contents(__DIR__ . '/../Keys/id_rsa.pub');
253+
}
191254
}

0 commit comments

Comments
 (0)
Please sign in to comment.