diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b918ebc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +CHANGELOG +========= + +This changelog will be used for bugfix for this version + +# 0.7 + +* add property accessor to change node path for token and expire fields diff --git a/README.md b/README.md index d152fe7..a6a8bb9 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ $authStrategy = new JsonAuthStrategy( ## Persistence -To avoid requesting a token everytime php runs, you can pass to `JwtManager` an implementation of `TokenPersistenceInterface`. +To avoid requesting a token everytime php runs, you can pass to `JwtManager` an implementation of `TokenPersistenceInterface`. By default `NullTokenPersistence` will be used. ### Simpe cache adapter (PSR-16) @@ -217,6 +217,40 @@ class MyCustomPersistence implements TokenPersistenceInterface ## Token key + +### Property accessor + +With the property accessor you can point to a node in your json. + +Json Example: + +```javascript +{ + "status": "success", + "message": "Login successful", + "payload": { + "token": "1453720507" + }, + "expires_in": 3600 +} +``` + +Library configuration: + +```php +$jwtManager = new JwtManager( + $authClient, + $authStrategy, + $persistenceStrategy, + [ + 'token_url' => '/api/token', + 'token_key' => 'payload.token', + 'expire_key' => 'expires_in' + ] +); +``` + +## Default behavior By default this library assumes your json response has a key `token`, something like this: ```javascript diff --git a/Tests/Manager/JwtManagerTest.php b/Tests/Manager/JwtManagerTest.php index 0af6435..70c6935 100644 --- a/Tests/Manager/JwtManagerTest.php +++ b/Tests/Manager/JwtManagerTest.php @@ -16,6 +16,123 @@ */ class JwtManagerTest extends \PHPUnit_Framework_TestCase { + + /** + * testGetTokenExpiredKeyException + */ + public function testGetTokenExpiredKeyException() + { + $mockHandler = new MockHandler([ + function (RequestInterface $request) { + + $this->assertTrue($request->hasHeader('timeout')); + $this->assertEquals( + 3, + $request->getHeaderLine('timeout') + ); + + $jsonPayload = << 'application/json'], + $jsonPayload + ); + }, + ]); + + $handler = HandlerStack::create($mockHandler); + + $authClient = new Client([ + 'handler' => $handler, + ]); + + $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']); + + $jwtManager = new JwtManager( + $authClient, + $authStrategy, + null, + [ + 'token_url' => '/api/token', + 'timeout' => 3, + 'token_key' => 'token', + 'expire_key' => 'expires_in' + ] + ); + + $this->setExpectedException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); + + $token = $jwtManager->getJwtToken(); + } + + /** + * testGetTokenWithSublevelResponse + */ + public function testGetTokenWithSublevelResponse() + { + $mockHandler = new MockHandler([ + function (RequestInterface $request) { + + $this->assertTrue($request->hasHeader('timeout')); + $this->assertEquals( + 3, + $request->getHeaderLine('timeout') + ); + + $jsonPayload = << 'application/json'], + $jsonPayload + ); + }, + ]); + + $handler = HandlerStack::create($mockHandler); + + $authClient = new Client([ + 'handler' => $handler, + ]); + + $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']); + + $jwtManager = new JwtManager( + $authClient, + $authStrategy, + null, + [ + 'token_url' => '/api/token', + 'timeout' => 3, + 'token_key' => 'payload.token', + 'expire_key' => 'expires_in' + ] + ); + $token = $jwtManager->getJwtToken(); + + $this->assertInstanceOf(JwtToken::class, $token); + $this->assertEquals('1453720507', $token->getToken()); + } + /** * testGetToken. */ @@ -33,7 +150,7 @@ function (RequestInterface $request) { return new Response( 200, ['Content-Type' => 'application/json'], - json_encode(['token' => '1453720507']) + json_encode(['token' => '1453720507', 'expires_in' => 3600]) ); }, ]); diff --git a/composer.json b/composer.json index 18d7131..8528bf0 100644 --- a/composer.json +++ b/composer.json @@ -24,14 +24,15 @@ "php" : ">=5.6.0", "guzzlehttp/guzzle": "~6.0", "psr/simple-cache": "^1.0", - "symfony/options-resolver": ">=2.8" + "symfony/options-resolver": ">=2.8", + "symfony/property-access":">=2.8" }, "config": { "bin-dir" : "bin/" }, "extra": { "branch-alias": { - "dev-master": "0.4-dev" + "dev-master": "0.7-dev" } } } diff --git a/src/JwtToken.php b/src/JwtToken.php index 134619b..c8e6582 100644 --- a/src/JwtToken.php +++ b/src/JwtToken.php @@ -15,7 +15,7 @@ class JwtToken private $token; /** - * @var \DateTime + * @var \DateTime|null */ private $expiration; diff --git a/src/Manager/JwtManager.php b/src/Manager/JwtManager.php index 0d2f1b4..16e6a88 100644 --- a/src/Manager/JwtManager.php +++ b/src/Manager/JwtManager.php @@ -9,6 +9,9 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\request; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; /** * @author Guillaume Cavana @@ -48,6 +51,12 @@ class JwtManager */ protected $tokenPersistence; + /** + * $propertyAccessor. + * + * @var PropertyAccessor + */ + protected $propertyAccessor; /** * Constructor. * @@ -81,6 +90,8 @@ public function __construct( $resolver->setRequired(['token_url', 'timeout']); $this->options = $resolver->resolve($options); + + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } /** @@ -95,7 +106,7 @@ public function getJwtToken() $this->token = $this->tokenPersistence->restoreToken(); } - if ($this->token && $this->token->isValid()) { + if ($this->token !== null && $this->token->isValid()) { return $this->token; } @@ -109,13 +120,20 @@ public function getJwtToken() ); $response = $this->client->request('POST', $url, $requestOptions); - $body = json_decode($response->getBody(), true); + $body = json_decode($response->getBody()); + + //Will be throw because it's mandatory + $tokenValue = $this->propertyAccessor->getValue($body, $this->options['token_key']); - $expiresIn = isset($body[$this->options['expire_key']]) ? $body[$this->options['expire_key']] : null; + try { + $expiresIn = $this->propertyAccessor->getValue($body, $this->options['expire_key']); + } catch (NoSuchPropertyException $e) { + $expiresIn = null; + } if ($expiresIn) { $expiration = new \DateTime('now + ' . $expiresIn . ' seconds'); - } elseif (count($jwtParts = explode('.', $body[$this->options['token_key']])) === 3 + } elseif (count($jwtParts = explode('.', $tokenValue)) === 3 && is_array($payload = json_decode(base64_decode($jwtParts[1]), true)) // https://tools.ietf.org/html/rfc7519.html#section-4.1.4 && array_key_exists('exp', $payload) @@ -126,7 +144,7 @@ public function getJwtToken() $expiration = null; } - $this->token = new JwtToken($body[$this->options['token_key']], $expiration); + $this->token = new JwtToken($tokenValue, $expiration); $this->tokenPersistence->saveToken($this->token); return $this->token;