diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b0b85ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1] - 2022-09-09 +### Changed +- Allow Symfony 6 dependency. + +## [0.4.0] - 2020-12-28 +### Changed +- Support for PHP 8 and require Guzzle 7. + +## [0.3.5] - 2020-06-04 +### Changed +- Allow Symfony 5 dependency. + +## [0.3.4] - 2019-04-05 +### Fixed +- `getClaims` is now actually returning the claims instead of always an empty array. + +## [0.3.3] - 2019-04-05 +### Added +- Getters the private key and hash algorithm. + +## [0.3.2] - 2019-04-05 +### Added +- Getters for the `Client` constructor parameters to ease testing. + +## [0.3.1] - 2018-07-17 +### Changed +- Allow Symfony 4 dependency. + +## [0.3.0] - 2018-05-31 +### Fixed +- Set return type of the payload to `array`. + +## [0.2.0] - 2018-05-30 +### Changed +- Always return a `Response` object. + +## [0.1.0] - 2018-05-29 +### Added +- Initial implementation of JWT API. diff --git a/composer.json b/composer.json index b8b92cb..bd1444c 100644 --- a/composer.json +++ b/composer.json @@ -28,10 +28,10 @@ } }, "require": { - "php": "^7.1", - "firebase/php-jwt": "^5.0", - "guzzlehttp/guzzle": "^6.3", - "symfony/http-foundation": "^3.4" + "php": "^7.2|^8.0", + "firebase/php-jwt": "^5.2", + "guzzlehttp/guzzle": "^7.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0|^6.0" }, "config": { "preferred-install": "dist", diff --git a/src/Client/Client.php b/src/Client/Client.php index 336d054..4b39343 100644 --- a/src/Client/Client.php +++ b/src/Client/Client.php @@ -10,8 +10,7 @@ class Client { - const CLIENT = 'JwtApi/PHP'; - const VERSION = 0.1; + const VERSION = '0.3.4'; const DEFAULT_HASH_ALGORITHM = 'RS256'; const HEADER_API_KEY = 'API-Key'; @@ -31,7 +30,7 @@ class Client private $claims; /** - * @var string + * @var string|null */ private $hashAlgorithm; @@ -41,7 +40,7 @@ class Client private $httpClient; /** - * @var string + * @var string|null */ private $privateKey; @@ -58,14 +57,29 @@ public function __construct(string $apiUrl, string $apiKey, array $claims = []) ])); } - protected function setHttpClient(HttpClient $httpClient): void + public function getApiUrl(): string { - $this->httpClient = $httpClient; + return $this->apiUrl; + } + + public function getApiKey(): string + { + return $this->apiKey; + } + + public function getPrivateKey(): ?string + { + return $this->privateKey; + } + + public function getHashAlgorithm(): ?string + { + return $this->hashAlgorithm; } public function getClaims(): array { - return []; + return $this->claims; } public function setClaims(array $claims): void @@ -73,6 +87,11 @@ public function setClaims(array $claims): void $this->claims = $claims; } + public function setHttpClient(HttpClient $httpClient): void + { + $this->httpClient = $httpClient; + } + public function loadPrivateKey(string $path, string $hashAlgorithm = self::DEFAULT_HASH_ALGORITHM): void { $this->setPrivateKey(file_get_contents($path), $hashAlgorithm); @@ -101,7 +120,8 @@ protected function getRequestOptions(Request $request): array $options = [ RequestOptions::HEADERS => [ static::HEADER_API_KEY => $this->apiKey, - 'Authorization' => "Bearer {$this->createAccessToken()}" + 'Authorization' => "Bearer {$this->createAccessToken()}", + 'Accept' => 'application/json' ] ]; @@ -113,9 +133,12 @@ protected function getRequestOptions(Request $request): array $options[RequestOptions::JSON] = $payload; } - return array_merge($options, $request->getRequestOptions()); + return array_merge_recursive($options, $request->getRequestOptions()); } + /** + * @throws RequestException + */ public function send(Request $request): Response { try { @@ -125,11 +148,7 @@ public function send(Request $request): Response $this->getRequestOptions($request) ); - if (($statusCode = $response->getStatusCode()) >= 200 && $statusCode < 300) { - return new Response($response); - } - - throw new RequestException($response->getBody()); + return new Response($response); } catch (ClientException $exception) { throw new RequestException($exception->getMessage()); } @@ -137,6 +156,6 @@ public function send(Request $request): Response public static function version(): string { - return static::CLIENT.'/'.static::VERSION; + return 'JwtApi/'.self::VERSION; } } diff --git a/src/Client/Exceptions/ClientException.php b/src/Client/Exceptions/ClientException.php index fe3496e..fb8a83d 100644 --- a/src/Client/Exceptions/ClientException.php +++ b/src/Client/Exceptions/ClientException.php @@ -6,26 +6,8 @@ abstract class ClientException extends RuntimeException { - /** - * @var array - */ - private $errors = []; - public function __construct($message, $code = 0, Exception $previous = null) { - // Try to decode into a JwtApi error response. - $response = json_decode($message); - - if ($response !== null) { - $this->errors = $response->errors ?? []; - - if (count($this->errors) > 0) { - $error = $this->errors[0]; - - $message = "[{$error->code}] {$error->message}"; - } - } - parent::__construct($message, $code, $previous); } } diff --git a/src/Client/Request.php b/src/Client/Request.php index e963d22..a8b5ad6 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -9,7 +9,7 @@ abstract class Request protected $parameters; /** - * @var string|null + * @var array|null */ protected $payload; @@ -25,7 +25,7 @@ public function getParameters(): ?array return $this->parameters; } - public function getPayload(): ?string + public function getPayload(): ?array { return $this->payload; } diff --git a/src/Client/Response.php b/src/Client/Response.php index da8f84c..9c34fe2 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -11,15 +11,34 @@ class Response */ private $data; + /** + * @var ResponseInterface + */ + private $response; + public function __construct(ResponseInterface $response) { - if (($this->data = json_decode($response->getBody(), true)) === null) { - throw new ResponseException("Could not decode response"); + $this->response = $response; + + if (($this->data = json_decode($body = $response->getBody(), true)) === null) { + throw new ResponseException("Could not decode response, expected valid JSON. Raw response: {$body}"); } } + public function isSuccessful(): bool + { + $statusCode = $this->response->getStatusCode(); + + return $statusCode >= 200 && $statusCode < 300; + } + public function getData(): array { return $this->data; } + + public function getResponse(): ResponseInterface + { + return $this->response; + } } diff --git a/src/Server/Exceptions/RequestException.php b/src/Server/Exceptions/RequestException.php index f6bd8aa..c6d4b94 100644 --- a/src/Server/Exceptions/RequestException.php +++ b/src/Server/Exceptions/RequestException.php @@ -31,7 +31,7 @@ public static function missingBearerToken(string $message = "No Bearer token fou return new static($message, static::MISSING_BEARER_TOKEN); } - public static function unresolvedPublicKey(string $message = "Could not resolve public key"): self + public static function unresolvedPublicKey(string $message = "Invalid API key"): self { return new static($message, static::UNRESOLVED_PUBLIC_KEY); } diff --git a/src/Server/RequestParser.php b/src/Server/RequestParser.php index 5b3160d..0e2e3b4 100644 --- a/src/Server/RequestParser.php +++ b/src/Server/RequestParser.php @@ -39,7 +39,7 @@ class RequestParser private $publicKeyResolver; /** - * TIll how long after issuance are tokens expected. + * Expiration time of tokens. * * @var int in seconds */ @@ -69,7 +69,7 @@ public function __construct(Closure $publicKeyResolver, int $expiration = 60, in /** * @throws RequestException */ - public function setRequest(Request $request, $headerName = Client::HEADER_API_KEY): void + public function setRequest(Request $request, string $headerName = Client::HEADER_API_KEY): void { $this->request = $request; $this->apiKey = static::extractApiKey($request, $headerName);