From d0fc1f71800393f1af787d8da6118357415d6099 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 14:44:45 -0300 Subject: [PATCH 01/12] refactor: use PHP ^8.0 and update components --- .vscode/settings.json | 3 + composer.json | 8 +- src/JwtAuthentication.php | 322 ++++------------------- src/JwtAuthentication/JwtAuthOptions.php | 144 ++++++++++ 4 files changed, 204 insertions(+), 273 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/JwtAuthentication/JwtAuthOptions.php diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2af03ac --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "php.version": "8.1" +} \ No newline at end of file diff --git a/composer.json b/composer.json index 8605cad..bd2378b 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,11 @@ } ], "require": { - "php": "^7.2|^8.0", - "psr/log": "^1.0|^2.0|^3.0", - "firebase/php-jwt": "^3.0|^4.0|^5.0", + "php": "^8.0", + "psr/log": "^3.0", + "firebase/php-jwt": "^5.0", "psr/http-message": "^1.0", - "tuupola/http-factory": "^1.3", + "tuupola/http-factory": "^1.4", "tuupola/callable-handler": "^1.0", "psr/http-server-middleware": "^1.0" }, diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index a152724..49b1e1e 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -34,9 +34,8 @@ namespace Tuupola\Middleware; -use Closure; use DomainException; -use InvalidArgumentException; +use Firebase\JWT\Key; use Exception; use Firebase\JWT\JWT; use Psr\Http\Message\ServerRequestInterface; @@ -47,8 +46,8 @@ use Psr\Log\LogLevel; use RuntimeException; use SplStack; -use Tuupola\Middleware\DoublePassTrait; use Tuupola\Http\Factory\ResponseFactory; +use Tuupola\Middleware\JwtAuthentication\JwtAuthOptions; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; use Tuupola\Middleware\JwtAuthentication\RuleInterface; @@ -59,91 +58,39 @@ final class JwtAuthentication implements MiddlewareInterface /** * PSR-3 compliant logger. - * @var LoggerInterface|null */ - private $logger; + private ?LoggerInterface $logger; /** * Last error message. - * @var string */ - private $message; + private string $message; /** * The rules stack. * @var SplStack */ - private $rules; + private SplStack $rules; - /** - * Stores all the options passed to the middleware. - * - * @var array{ - * secret?: string|array, - * secure: bool, - * relaxed: array, - * algorithm: array, - * header: string, - * regexp: string, - * cookie: string, - * attribute: string, - * path: array, - * ignore: array, - * before: null|callable, - * after: null|callable, - * error: null|callable, - * } - */ - private $options = [ - "secure" => true, - "relaxed" => ["localhost", "127.0.0.1"], - "algorithm" => ["HS256", "HS512", "HS384"], - "header" => "Authorization", - "regexp" => "/Bearer\s+(.*)$/i", - "cookie" => "token", - "attribute" => "token", - "path" => ["/"], - "ignore" => [], - "before" => null, - "after" => null, - "error" => null - ]; + private JwtAuthOptions $options; - /** - * @param array{ - * secret?: string|array, - * secure?: bool, - * relaxed?: array, - * algorithm?: array, - * header?: string, - * regexp?: string, - * cookie?: string, - * attribute?: string, - * path?: array, - * ignore?: array, - * before?: null|callable, - * after?: null|callable, - * error?: null|callable, - * } $options - */ - public function __construct(array $options = []) + public function __construct(JwtAuthOptions $options) { /* Setup stack for rules */ - $this->rules = new \SplStack; + $this->rules = new SplStack; - /* Store passed in options overwriting any defaults. */ - $this->hydrate($options); + $this->options = $options->bindToAuthentication($this); /* If nothing was passed in options add default rules. */ - /* This also means $options["rules"] overrides $options["path"] */ - /* and $options["ignore"] */ - if (!isset($options["rules"])) { + /* This also means $options->rules overrides $options->path */ + /* and $options->ignore */ + if (!isset($options->rules)) { $this->rules->push(new RequestMethodRule([ "ignore" => ["OPTIONS"] ])); $this->rules->push(new RequestPathRule([ - "path" => $this->options["path"], - "ignore" => $this->options["ignore"] + "path" => $this->options->path, + "ignore" => $this->options->ignore ])); } } @@ -157,13 +104,13 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $host = $request->getUri()->getHost(); /* If rules say we should not authenticate call next and return. */ - if (false === $this->shouldAuthenticate($request)) { + if (!$this->shouldAuthenticate($request)) { return $handler->handle($request); } /* HTTP allowed only if secure is false or server is in relaxed array. */ - if ("https" !== $scheme && true === $this->options["secure"]) { - if (!in_array($host, $this->options["relaxed"])) { + if ("https" !== $scheme && $this->options->secure) { + if (!in_array($host, $this->options->relaxed)) { $message = sprintf( "Insecure use of middleware over %s denied by configuration.", strtoupper($scheme) @@ -178,9 +125,10 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $decoded = $this->decodeToken($token); } catch (RuntimeException | DomainException $exception) { $response = (new ResponseFactory)->createResponse(401); + return $this->processError($response, [ "message" => $exception->getMessage(), - "uri" => (string)$request->getUri() + "uri" => (string) $request->getUri() ]); } @@ -190,29 +138,28 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ]; /* Add decoded token to request as attribute when requested. */ - if ($this->options["attribute"]) { - $request = $request->withAttribute($this->options["attribute"], $decoded); + if ($this->options->attribute) { + $request = $request->withAttribute($this->options->attribute, $decoded); } /* Modify $request before calling next middleware. */ - if (is_callable($this->options["before"])) { - $beforeRequest = $this->options["before"]($request, $params); - if ($beforeRequest instanceof ServerRequestInterface) { - $request = $beforeRequest; - } + $beforeRequest = $this->options->onBeforeCallable($request, $params); + + if ($beforeRequest instanceof ServerRequestInterface) { + $request = $beforeRequest; } /* Everything ok, call next middleware. */ $response = $handler->handle($request); /* Modify $response before returning. */ - if (is_callable($this->options["after"])) { - $afterResponse = $this->options["after"]($response, $params); - if ($afterResponse instanceof ResponseInterface) { - return $afterResponse; - } + $afterResponse = $this->options->onAfterCallable($response, $params); + + if ($afterResponse) { + return $afterResponse; } + return $response; } @@ -226,11 +173,12 @@ public function withRules(array $rules): self $new = clone $this; /* Clear the stack */ unset($new->rules); - $new->rules = new \SplStack; + $new->rules = new SplStack; /* Add the rules */ foreach ($rules as $callable) { $new = $new->addRule($callable); } + return $new; } @@ -242,6 +190,7 @@ public function addRule(callable $callable): self $new = clone $this; $new->rules = clone $this->rules; $new->rules->push($callable); + return $new; } @@ -252,10 +201,11 @@ private function shouldAuthenticate(ServerRequestInterface $request): bool { /* If any of the rules in stack return false will not authenticate */ foreach ($this->rules as $callable) { - if (false === $callable($request)) { + if (!$callable($request)) { return false; } } + return true; } @@ -266,13 +216,7 @@ private function shouldAuthenticate(ServerRequestInterface $request): bool */ private function processError(ResponseInterface $response, array $arguments): ResponseInterface { - if (is_callable($this->options["error"])) { - $handlerResponse = $this->options["error"]($response, $arguments); - if ($handlerResponse instanceof ResponseInterface) { - return $handlerResponse; - } - } - return $response; + return $this->options->onError($response, $arguments) ?? $response; } /** @@ -281,10 +225,10 @@ private function processError(ResponseInterface $response, array $arguments): Re private function fetchToken(ServerRequestInterface $request): string { /* Check for token in header. */ - $header = $request->getHeaderLine($this->options["header"]); + $header = $request->getHeaderLine($this->options->header); - if (false === empty($header)) { - if (preg_match($this->options["regexp"], $header, $matches)) { + if (!empty($header)) { + if (preg_match($this->options->regexp, $header, $matches)) { $this->log(LogLevel::DEBUG, "Using token from request header"); return $matches[1]; } @@ -293,143 +237,42 @@ private function fetchToken(ServerRequestInterface $request): string /* Token not found in header try a cookie. */ $cookieParams = $request->getCookieParams(); - if (isset($cookieParams[$this->options["cookie"]])) { + if (isset($cookieParams[$this->options->cookie])) { $this->log(LogLevel::DEBUG, "Using token from cookie"); - if (preg_match($this->options["regexp"], $cookieParams[$this->options["cookie"]], $matches)) { + if (preg_match($this->options->regexp, $cookieParams[$this->options->cookie], $matches)) { return $matches[1]; } - return $cookieParams[$this->options["cookie"]]; - }; + return $cookieParams[$this->options->cookie]; + } + ; /* If everything fails log and throw. */ $this->log(LogLevel::WARNING, "Token not found"); + throw new RuntimeException("Token not found."); } - /** - * Decode the token. - * - * @return mixed[] - */ private function decodeToken(string $token): array { + $algo = $this->options->algorithm; + try { $decoded = JWT::decode( $token, - $this->options["secret"], - (array) $this->options["algorithm"] + new Key( + $this->options->secret, + $algo + ) ); + return (array) $decoded; } catch (Exception $exception) { $this->log(LogLevel::WARNING, $exception->getMessage(), [$token]); - throw $exception; - } - } - - /** - * Hydrate options from given array. - * - * @param mixed[] $data - */ - private function hydrate(array $data = []): void - { - foreach ($data as $key => $value) { - /* https://github.com/facebook/hhvm/issues/6368 */ - $key = str_replace(".", " ", $key); - $method = lcfirst(ucwords($key)); - $method = str_replace(" ", "", $method); - if (method_exists($this, $method)) { - /* Try to use setter */ - /** @phpstan-ignore-next-line */ - call_user_func([$this, $method], $value); - } else { - /* Or fallback to setting option directly */ - $this->options[$key] = $value; - } - } - } - - /** - * Set path where middleware should bind to. - * - * @param string|string[] $path - */ - private function path($path): void - { - $this->options["path"] = (array) $path; - } - - /** - * Set path which middleware ignores. - * - * @param string|string[] $ignore - */ - private function ignore($ignore): void - { - $this->options["ignore"] = (array) $ignore; - } - - /** - * Set the cookie name where to search the token from. - */ - private function cookie(string $cookie): void - { - $this->options["cookie"] = $cookie; - } - - /** - * Set the secure flag. - */ - private function secure(bool $secure): void - { - $this->options["secure"] = $secure; - } - - /** - * Set hosts where secure rule is relaxed. - * - * @param string[] $relaxed - */ - private function relaxed(array $relaxed): void - { - $this->options["relaxed"] = $relaxed; - } - - /** - * Set the secret key. - * - * @param string|string[] $secret - */ - private function secret($secret): void - { - if (false === is_array($secret) && false === is_string($secret) && ! $secret instanceof \ArrayAccess) { - throw new InvalidArgumentException( - 'Secret must be either a string or an array of "kid" => "secret" pairs' - ); - } - $this->options["secret"] = $secret; - } - /** - * Set the error handler. - */ - private function error(callable $error): void - { - if ($error instanceof Closure) { - $this->options["error"] = $error->bindTo($this); - } else { - $this->options["error"] = $error; + throw $exception; } } - /** - * Set the logger. - */ - private function logger(LoggerInterface $logger = null): void - { - $this->logger = $logger; - } - /** * Logs with an arbitrary level. * @@ -442,65 +285,6 @@ private function log(string $level, string $message, array $context = []): void } } - /** - * Set the attribute name used to attach decoded token to request. - */ - private function attribute(string $attribute): void - { - $this->options["attribute"] = $attribute; - } - - /** - * Set the header where token is searched from. - */ - private function header(string $header): void - { - $this->options["header"] = $header; - } - - /** - * Set the regexp used to extract token from header or environment. - */ - private function regexp(string $regexp): void - { - $this->options["regexp"] = $regexp; - } - - /** - * Set the allowed algorithms - * - * @param string|string[] $algorithm - */ - private function algorithm($algorithm): void - { - $this->options["algorithm"] = (array) $algorithm; - } - - /** - * Set the before handler. - */ - - private function before(callable $before): void - { - if ($before instanceof Closure) { - $this->options["before"] = $before->bindTo($this); - } else { - $this->options["before"] = $before; - } - } - - /** - * Set the after handler. - */ - private function after(callable $after): void - { - if ($after instanceof Closure) { - $this->options["after"] = $after->bindTo($this); - } else { - $this->options["after"] = $after; - } - } - /** * Set the rules. * @param RuleInterface[] $rules diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php new file mode 100644 index 0000000..95bc84b --- /dev/null +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -0,0 +1,144 @@ + */ + public readonly array $relaxed; + public readonly string $algorithm; + public readonly string $header; + public readonly string $regexp; + public readonly string $cookie; + public readonly string $attribute; + /** @var array */ + public readonly array $path; + + /** @var RuleInterface[] $rules */ + public readonly array $rules; + + /** @var array */ + public readonly array $ignore; + public readonly ?\Closure $before; + public readonly ?\Closure $after; + public readonly ?\Closure $error; + + private JwtAuthentication $jwtAuthentication; + + public function __construct( + string $secret, + bool $secure = true, + array $relaxed = ["localhost", "127.0.0.1"], + string $algorithm = "HS256", + string $header = "Authorization", + string $regexp = "/Bearer\s+(.*)$/i", + string $cookie = "token", + string $attribute = "token", + array $path = ["/"], + array $ignore = [], + array $rules = [], + ?callable $before = null, + ?callable $after = null, + ?callable $error = null + ) { + $this->secret = $this->checkSecret($secret); + $this->secure = $secure; + $this->relaxed = $relaxed; + $this->algorithm = $algorithm; + $this->header = $header; + $this->regexp = $regexp; + $this->cookie = $cookie; + $this->attribute = $attribute; + $this->path = $path; + $this->rules = $rules; + $this->ignore = $ignore; + $this->before = $before; + $this->after = $after; + $this->error = $error; + } + + private function checkSecret($secret): string + { + if (false === is_array($secret) && false === is_string($secret) && !$secret instanceof \ArrayAccess) { + throw new InvalidArgumentException( + 'Secret must be either a string or an array of "kid" => "secret" pairs' + ); + } + return $secret; + } + + public function bindToAuthentication(JwtAuthentication $target): self + { + $this->jwtAuthentication = $target; + + return $this; + } + + /** + * Set the error handler. + */ + public function onError(ResponseInterface $response, array $arguments): ?ResponseInterface + { + return $this->error?->call($this->jwtAuthentication, $response, $arguments); + } + + /** + * Set the before handler. + */ + + public function onBeforeCallable(ServerRequestInterface $request, array $params): ?ServerRequestInterface + { + return $this->before?->call($this->jwtAuthentication, $request, $params); + } + /** + * Set the after handler. + */ + public function onAfterCallable(ResponseInterface $response, array $params): ?ResponseInterface + { + return $this->before?->call($this->jwtAuthentication, $response, $params); + } +} From 5801fb9f1c4ddc9c5683203f92d2a0aa9078ed52 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 14:47:21 -0300 Subject: [PATCH 02/12] fix: remove readonly keyword --- UPGRADING.md | 1 + src/JwtAuthentication/JwtAuthOptions.php | 28 ++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 01adc49..14dd727 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,7 @@ # Updgrading from 2.x to 3.x ## New namespace + For most cases it is enough just to update the classname. Instead of using the old `Slim\Middleware` namespace: ```php diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index 95bc84b..2139b02 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -44,28 +44,28 @@ */ class JwtAuthOptions { - public readonly string $secret; + public string $secret; - public readonly bool $secure; + public bool $secure; /** @var array */ - public readonly array $relaxed; - public readonly string $algorithm; - public readonly string $header; - public readonly string $regexp; - public readonly string $cookie; - public readonly string $attribute; + public array $relaxed; + public string $algorithm; + public string $header; + public string $regexp; + public string $cookie; + public string $attribute; /** @var array */ - public readonly array $path; + public array $path; /** @var RuleInterface[] $rules */ - public readonly array $rules; + public array $rules; /** @var array */ - public readonly array $ignore; - public readonly ?\Closure $before; - public readonly ?\Closure $after; - public readonly ?\Closure $error; + public array $ignore; + public ?\Closure $before; + public ?\Closure $after; + public ?\Closure $error; private JwtAuthentication $jwtAuthentication; From 081ebd03e37cd688cd3c778858d87cd044ba9aaf Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 16:47:52 -0300 Subject: [PATCH 03/12] fix: solve problems in tests --- README.md | 15 +- UPGRADING.md | 40 +- src/JwtAuthentication.php | 30 +- src/JwtAuthentication/JwtAuthOptions.php | 42 +- tests/JwtAuthenticationTest.php | 845 +++++++++++++---------- 5 files changed, 546 insertions(+), 426 deletions(-) diff --git a/README.md b/README.md index e4152cb..1e5fbf4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ For example implementation see [Slim API Skeleton](https://github.com/tuupola/sl Install latest version using [composer](https://getcomposer.org/). ``` bash -$ composer require tuupola/slim-jwt-auth +composer require tuupola/slim-jwt-auth ``` If using Apache add the following to the `.htaccess` file. Otherwise PHP wont have access to `Authorization: Bearer` header. @@ -56,8 +56,8 @@ When a request is made, the middleware tries to validate and decode the token. I Validation errors are triggered when the token has been tampered with or the token has expired. For all possible validation errors, see [JWT library](https://github.com/firebase/php-jwt/blob/master/src/JWT.php#L60-L138) source. - ## Optional parameters + ### Path The optional `path` parameter allows you to specify the protected part of your website. It can be either a string or an array. You do not need to specify each URL. Instead think of `path` setting as a folder. In the example below everything starting with `/api` will be authenticated. If you do not define `path` all routes will be protected. @@ -195,7 +195,6 @@ $app->add(new Tuupola\Middleware\JwtAuthentication([ After function is called only when authentication succeeds and after the incoming middleware stack has been called. You can use this to alter the response before passing it next outgoing middleware in the stack. If it returns anything else than `Psr\Http\Message\ResponseInterface` the return value will be ignored. - ``` php $app = new Slim\App; @@ -257,7 +256,7 @@ RequestPathRule contains both a `path` parameter and a `ignore` parameter. Latte 99% of the cases you do not need to use the `rules` parameter. It is only provided for special cases when defaults do not suffice. -## Security +## Security in Tokens JSON Web Tokens are essentially passwords. You should treat them as such and you should always use HTTPS. If the middleware detects insecure usage over HTTP it will throw a `RuntimeException`. By default this rule is relaxed for requests to server running on `localhost`. To allow insecure usage you must enable it manually by setting `secure` to `false`. @@ -317,12 +316,12 @@ $app->delete("/item/{id}", function ($request, $response, $arguments) { You can run tests either manually or automatically on every code change. Automatic tests require [entr](http://entrproject.org/) to work. ``` bash -$ make test +make test ``` ``` bash -$ brew install entr -$ make watch +brew install entr +make watch ``` ## Contributing @@ -331,7 +330,7 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ## Security -If you discover any security related issues, please email tuupola@appelsiini.net instead of using the issue tracker. +If you discover any security related issues, please email instead of using the issue tracker. ## License diff --git a/UPGRADING.md b/UPGRADING.md index 14dd727..ae3ec7f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -5,17 +5,22 @@ For most cases it is enough just to update the classname. Instead of using the old `Slim\Middleware` namespace: ```php -$app->add(new Slim\Middleware\JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" -])); +$app->add(new Slim\Middleware\JwtAuthentication( + new JwtAuthOptions( + secret: "supersecretkeyyoushouldnotcommittogithub" + ) + ) +); ``` You should now use `Tuupola\Middleware` instead: ```php -$app->add(new Tuupola\Middleware\JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" -])); +$app->add(new Tuupola\Middleware\JwtAuthentication( + new JwtAuthOptions( + secret: "supersecretkeyyoushouldnotcommittogithub" + ) +); ``` @@ -35,12 +40,15 @@ $app->add(new Tuupola\Middleware\JwtAuthentication([ You should now do the following instead. Note also that `$response` object is not bassed to `before` anymore. The `before` handler should return ``Psr\Http\Message\ServerRequestInterface`. Anything else will be ignored. ```php -$app->add(new Tuupola\Middleware\JwtAuthentication([ - "ignore" => ["/token"], - "before" => function ($request, $arguments) { +$options = new JwtAuthOptions( + secret: "supersecretkeyyoushouldnotcommittogithub", + ignore: ["/token"], + before: => function (ServerRequestInterface $request, array $arguments) { return $request->withHeader("Foo", "bar"); } -])); +); + +$app->add(new Tuupola\Middleware\JwtAuthentication($options)); ``` ## Changed error handler signature @@ -58,11 +66,13 @@ $app->add(new Tuupola\Middleware\JwtAuthentication([ You should now do the following instead. ```php -$app->add(new Tuupola\Middleware\JwtAuthentication([ - "error" => function ($response, $arguments) { +$options = new JwtAuthOptions( + error: function (ReponseInterface $response, array $arguments): ResponseInterface { return $response->witHeader("Foo", "bar"); } -])); +); + +$app->add(new Tuupola\Middleware\JwtAuthentication($options); ``` Note that `error` should now return an instance of `Psr\Http\Message\ResponseInterface`. Anything else will be ignored. @@ -91,3 +101,7 @@ $app->add(new Tuupola\Middleware\JwtAuthentication([ ## Decoded token is now an array The decoded token attached to the `$request` object is now an array instead of an object. This might require changes to token handling code. + +## Algorithm is a string now + +Prefer using strings instead of array of strings, for compartibility with firebase/php-jwt. diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index 49b1e1e..97ca30d 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -74,17 +74,19 @@ final class JwtAuthentication implements MiddlewareInterface private JwtAuthOptions $options; - public function __construct(JwtAuthOptions $options) + public function __construct(JwtAuthOptions $options, ?LoggerInterface $logger = null) { /* Setup stack for rules */ $this->rules = new SplStack; + $this->logger = $logger; + $this->options = $options->bindToAuthentication($this); /* If nothing was passed in options add default rules. */ /* This also means $options->rules overrides $options->path */ /* and $options->ignore */ - if (!isset($options->rules)) { + if (!(isset($options->rules) && count($options->rules))) { $this->rules->push(new RequestMethodRule([ "ignore" => ["OPTIONS"] ])); @@ -104,7 +106,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $host = $request->getUri()->getHost(); /* If rules say we should not authenticate call next and return. */ - if (!$this->shouldAuthenticate($request)) { + if (false === $this->shouldAuthenticate($request)) { return $handler->handle($request); } @@ -144,7 +146,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /* Modify $request before calling next middleware. */ $beforeRequest = $this->options->onBeforeCallable($request, $params); - + if ($beforeRequest instanceof ServerRequestInterface) { $request = $beforeRequest; } @@ -154,7 +156,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /* Modify $response before returning. */ $afterResponse = $this->options->onAfterCallable($response, $params); - + if ($afterResponse) { return $afterResponse; } @@ -190,7 +192,7 @@ public function addRule(callable $callable): self $new = clone $this; $new->rules = clone $this->rules; $new->rules->push($callable); - + return $new; } @@ -248,21 +250,23 @@ private function fetchToken(ServerRequestInterface $request): string /* If everything fails log and throw. */ $this->log(LogLevel::WARNING, "Token not found"); - + throw new RuntimeException("Token not found."); } private function decodeToken(string $token): array { - $algo = $this->options->algorithm; - try { + $algo = $this->options->algorithm; + + $key = new Key( + keyMaterial: $this->options->secret, + algorithm: $algo + ); + $decoded = JWT::decode( $token, - new Key( - $this->options->secret, - $algo - ) + $key ); return (array) $decoded; diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index 2139b02..3f1d9b0 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -37,6 +37,7 @@ use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Tuupola\Middleware\ArrayAccessImpl; use Tuupola\Middleware\JwtAuthentication; /** @@ -63,14 +64,14 @@ class JwtAuthOptions /** @var array */ public array $ignore; - public ?\Closure $before; - public ?\Closure $after; - public ?\Closure $error; + public $before; + public $after; + public $error; private JwtAuthentication $jwtAuthentication; public function __construct( - string $secret, + string|array|ArrayAccessImpl $secret, bool $secure = true, array $relaxed = ["localhost", "127.0.0.1"], string $algorithm = "HS256", @@ -101,9 +102,9 @@ public function __construct( $this->error = $error; } - private function checkSecret($secret): string + private function checkSecret($secret): string|array { - if (false === is_array($secret) && false === is_string($secret) && !$secret instanceof \ArrayAccess) { + if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { throw new InvalidArgumentException( 'Secret must be either a string or an array of "kid" => "secret" pairs' ); @@ -111,10 +112,27 @@ private function checkSecret($secret): string return $secret; } + private function bindClosure(?callable $closure, JwtAuthentication $target): ?\Closure + { + if ($closure) { + if ($closure instanceof \Closure) { + return $closure->bindTo($target); + } + + return \Closure::fromCallable($closure); + } + + return null; + } + public function bindToAuthentication(JwtAuthentication $target): self { $this->jwtAuthentication = $target; + $this->error = $this->bindClosure($this->error, $target); + $this->before = $this->bindClosure($this->before, $target); + $this->after = $this->bindClosure($this->after, $target); + return $this; } @@ -123,7 +141,9 @@ public function bindToAuthentication(JwtAuthentication $target): self */ public function onError(ResponseInterface $response, array $arguments): ?ResponseInterface { - return $this->error?->call($this->jwtAuthentication, $response, $arguments); + $func = $this->error; + + return is_null($func) ? null : $func($response, $arguments); } /** @@ -132,13 +152,17 @@ public function onError(ResponseInterface $response, array $arguments): ?Respons public function onBeforeCallable(ServerRequestInterface $request, array $params): ?ServerRequestInterface { - return $this->before?->call($this->jwtAuthentication, $request, $params); + $func = $this->before; + + return is_null($func) ? null : $func($request, $params); } /** * Set the after handler. */ public function onAfterCallable(ResponseInterface $response, array $params): ?ResponseInterface { - return $this->before?->call($this->jwtAuthentication, $response, $params); + $func = $this->after; + + return is_null($func) ? null : $func($response, $params); } } diff --git a/tests/JwtAuthenticationTest.php b/tests/JwtAuthenticationTest.php index 4ac9652..63e0d2a 100644 --- a/tests/JwtAuthenticationTest.php +++ b/tests/JwtAuthenticationTest.php @@ -39,6 +39,7 @@ use Tuupola\Http\Factory\ResponseFactory; use Tuupola\Http\Factory\ServerRequestFactory; use Tuupola\Http\Factory\StreamFactory; +use Tuupola\Middleware\JwtAuthentication\JwtAuthOptions; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; @@ -56,7 +57,7 @@ class JwtAuthenticationTest extends TestCase "exp" => "1744352741", "aud" => "www.example.com", "sub" => "someone@example.com", - "scope" => ["read", "write", "delete"] + "scope" => ["read", "write", "delete"], ]; public static $betaTokenArray = [ @@ -65,24 +66,27 @@ class JwtAuthenticationTest extends TestCase "exp" => "1744352741", "aud" => "www.example.com", "sub" => "someone@example.com", - "scope" => ["read"] + "scope" => ["read"], ]; public function testShouldReturn401WithoutToken() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); + + $default = function (ServerRequestInterface $request) { + $response = (new ResponseFactory())->createResponse(); - $default = function (RequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); $response->getBody()->write("Success"); return $response; }; - + $options = new JwtAuthOptions( + secret: "supersecretkeyyoushouldnotcommittogithub" + ); $collection = new MiddlewareCollection([ - $auth = new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication($options), ]); $response = $collection->dispatch($request, $default); @@ -93,21 +97,25 @@ public function testShouldReturn401WithoutToken() public function testShouldReturn200WithTokenFromHeader() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("X-Token", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; - $collection = new MiddlewareCollection([ - new JwtAuthentication([ + $options = new JwtAuthOptions( + ...[ "secret" => "supersecretkeyyoushouldnotcommittogithub", - "header" => "X-Token" - ]) + "header" => "X-Token", + ] + ); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options), ]); $response = $collection->dispatch($request, $default); @@ -118,22 +126,26 @@ public function testShouldReturn200WithTokenFromHeader() public function testShouldReturn200WithTokenFromHeaderWithCustomRegexp() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("X-Token", self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; - $collection = new MiddlewareCollection([ - new JwtAuthentication([ + $options = new JwtAuthOptions( + ...[ "secret" => "supersecretkeyyoushouldnotcommittogithub", "header" => "X-Token", - "regexp" => "/(.*)/" - ]) + "regexp" => "/(.*)/", + ] + ); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options), ]); $response = $collection->dispatch($request, $default); @@ -144,21 +156,25 @@ public function testShouldReturn200WithTokenFromHeaderWithCustomRegexp() public function testShouldReturn200WithTokenFromCookie() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withCookieParams(["nekot" => self::$acmeToken]); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; - $collection = new MiddlewareCollection([ - new JwtAuthentication([ + $options = new JwtAuthOptions( + ...[ "secret" => "supersecretkeyyoushouldnotcommittogithub", "cookie" => "nekot", - ]) + ] + ); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options), ]); $response = $collection->dispatch($request, $default); @@ -169,180 +185,93 @@ public function testShouldReturn200WithTokenFromCookie() public function testShouldReturn200WithTokenFromBearerCookie() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withCookieParams(["nekot" => "Bearer " . self::$acmeToken]); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; - $collection = new MiddlewareCollection([ - new JwtAuthentication([ + $options = new JwtAuthOptions( + ...[ "secret" => "supersecretkeyyoushouldnotcommittogithub", "cookie" => "nekot", - ]) - ]); - - $response = $collection->dispatch($request, $default); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals("Success", $response->getBody()); - } - - - public function testShouldReturn200WithSecretArray() - { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api") - ->withHeader("Authorization", "Bearer " . self::$betaToken); - - $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); - $response->getBody()->write("Success"); - return $response; - }; - - $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => [ - "acme" =>"supersecretkeyyoushouldnotcommittogithub", - "beta" =>"anothersecretkeyfornevertocommittogithub" - ] - ]) - ]); - - $response = $collection->dispatch($request, $default); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals("Success", $response->getBody()); - } - - public function testShouldReturn401WithSecretArray() - { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api") - ->withHeader("Authorization", "Bearer " . self::$betaToken); - - $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); - $response->getBody()->write("Success"); - return $response; - }; + ] + ); $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => [ - "xxxx" =>"supersecretkeyyoushouldnotcommittogithub", - "yyyy" =>"anothersecretkeyfornevertocommittogithub" - ] - ]) + new JwtAuthentication($options), ]); $response = $collection->dispatch($request, $default); - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals("", $response->getBody()); - } - - public function testShouldReturn200WithSecretArrayAccess() - { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api") - ->withHeader("Authorization", "Bearer " . self::$betaToken); - - $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); - $response->getBody()->write("Success"); - return $response; - }; - - $secret = new ArrayAccessImpl(); - $secret["acme"] = "supersecretkeyyoushouldnotcommittogithub"; - $secret["beta"] ="anothersecretkeyfornevertocommittogithub"; - $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => $secret - ]) - ]); - - $response = $collection->dispatch($request, $default); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals("Success", $response->getBody()); } - public function testShouldReturn401WithSecretArrayAccess() - { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api") - ->withHeader("Authorization", "Bearer " . self::$betaToken); - - $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); - $response->getBody()->write("Success"); - return $response; - }; - - $secret = new ArrayAccessImpl(); - $secret["xxxx"] = "supersecretkeyyoushouldnotcommittogithub"; - $secret["yyyy"] = "anothersecretkeyfornevertocommittogithub"; - - $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => $secret - ]) - ]); - - $response = $collection->dispatch($request, $default); - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals("", $response->getBody()); - } + public function testShouldAlterResponseWithAnonymousAfter() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "after" => function ($response, $arguments) { - return $response->withHeader("X-Brawndo", "plants crave"); - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "after" => function ($response, $arguments) { + return $response->withHeader( + "X-Brawndo", + "plants crave" + ); + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals("plants crave", (string) $response->getHeaderLine("X-Brawndo")); + $this->assertEquals( + "plants crave", + (string) $response->getHeaderLine("X-Brawndo") + ); } public function testShouldAlterResponseWithInvokableAfter() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "after" => new TestAfterHandler - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "after" => new TestAfterHandler(), + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -356,21 +285,25 @@ public function testShouldAlterResponseWithInvokableAfter() public function testShouldAlterResponseWithArrayNotationAfter() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "after" => [TestAfterHandler::class, "after"] - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "after" => [TestAfterHandler::class, "after"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -384,21 +317,25 @@ public function testShouldAlterResponseWithArrayNotationAfter() public function testShouldReturn401WithInvalidAlgorithm() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "algorithm" => "nosuch", - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "algorithm" => "nosuch", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -409,20 +346,24 @@ public function testShouldReturn401WithInvalidAlgorithm() public function testShouldReturn200WithOptions() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withMethod("OPTIONS"); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -433,20 +374,24 @@ public function testShouldReturn200WithOptions() public function testShouldReturn400WithInvalidToken() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer invalid" . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -457,20 +402,24 @@ public function testShouldReturn400WithInvalidToken() public function testShouldReturn400WithExpiredToken() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$expired); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -481,45 +430,57 @@ public function testShouldReturn400WithExpiredToken() public function testShouldReturn200WithoutTokenWithPath() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/public"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/public" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "path" => ["/api", "/foo"], - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "path" => ["/api", "/foo"], + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals("Success", $response->getBody()); + $this->assertEquals("Success", $response->getBody()->__toString()); } public function testShouldReturn200WithoutTokenWithIgnore() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api/ping"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api/ping" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "path" => ["/api", "/foo"], - "ignore" => ["/api/ping"], - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "path" => ["/api", "/foo"], + "ignore" => ["/api/ping"], + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -532,20 +493,24 @@ public function testShouldNotAllowInsecure() { $this->expectException("RuntimeException"); - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "http://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -553,21 +518,25 @@ public function testShouldNotAllowInsecure() public function testShouldAllowInsecure() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "http://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "secure" => false - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "secure" => false, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -578,20 +547,24 @@ public function testShouldAllowInsecure() public function testShouldRelaxInsecureInLocalhost() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "http://localhost/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -602,21 +575,25 @@ public function testShouldRelaxInsecureInLocalhost() public function testShouldRelaxInsecureInExampleCom() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "http://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "relaxed" => ["example.com"], - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "relaxed" => ["example.com"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -627,23 +604,27 @@ public function testShouldRelaxInsecureInExampleCom() public function testShouldAttachToken() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { $acmeToken = $request->getAttribute("token"); - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write($acmeToken["iss"]); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -654,24 +635,28 @@ public function testShouldAttachToken() public function testShouldAttachCustomToken() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { $acmeToken = $request->getAttribute("nekot"); - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write($acmeToken["iss"]); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "attribute" => "nekot" - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "attribute" => "nekot", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -682,7 +667,7 @@ public function testShouldAttachCustomToken() public function testShouldCallAfterWithProperArguments() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); @@ -690,19 +675,23 @@ public function testShouldCallAfterWithProperArguments() $token = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "after" => function ($response, $arguments) use (&$decoded, &$token) { - $decoded = $arguments["decoded"]; - $token = $arguments["token"]; - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "after" => function ($response, $arguments) use (&$decoded, &$token) { + $decoded = $arguments["decoded"]; + $token = $arguments["token"]; + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -715,7 +704,7 @@ public function testShouldCallAfterWithProperArguments() public function testShouldCallBeforeWithProperArguments() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); @@ -723,19 +712,23 @@ public function testShouldCallBeforeWithProperArguments() $token = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "before" => function ($response, $arguments) use (&$decoded, &$token) { - $decoded = $arguments["decoded"]; - $token = $arguments["token"]; - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "before" => function ($response, $arguments) use (&$decoded, &$token) { + $decoded = $arguments["decoded"]; + $token = $arguments["token"]; + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -748,51 +741,68 @@ public function testShouldCallBeforeWithProperArguments() public function testShouldCallAnonymousErrorFunction() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommit", - "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { - $response->getBody()->write("error"); - return $response - ->withHeader("X-Electrolytes", "Plants"); - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommit", + "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { + $response->getBody()->write("error"); + return $response->withHeader( + "X-Electrolytes", + "Plants" + ); + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals("Plants", $response->getHeaderLine("X-Electrolytes")); + $this->assertEquals( + "Plants", + $response->getHeaderLine("X-Electrolytes") + ); $this->assertEquals("error", $response->getBody()); } public function testShouldCallInvokableErrorClass() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $dummy = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommit", - "error" => new TestErrorHandler - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommit", + "error" => new TestErrorHandler(), + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -804,22 +814,28 @@ public function testShouldCallInvokableErrorClass() public function testShouldCallArrayNotationError() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $dummy = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommit", - "error" => [TestErrorHandler::class, "error"] - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommit", + "error" => [TestErrorHandler::class, "error"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -831,26 +847,32 @@ public function testShouldCallArrayNotationError() public function testShouldCallErrorAndModifyBody() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $dummy = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { - $dummy = true; - $response->getBody()->write("Error"); - return $response; - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { + $dummy = true; + $response->getBody()->write("Error"); + return $response; + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -862,21 +884,26 @@ public function testShouldCallErrorAndModifyBody() public function testShouldAllowUnauthenticatedHttp() { - - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/public/foo"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/public/foo" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "path" => ["/api", "/bar"], - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "path" => ["/api", "/bar"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -887,25 +914,31 @@ public function testShouldAllowUnauthenticatedHttp() public function testShouldReturn401FromAfter() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "after" => function ($response, $arguments) { - return $response - ->withBody((new StreamFactory)->createStream()) - ->withStatus(401); - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "after" => function ($response, $arguments) { + return $response + ->withBody( + (new StreamFactory())->createStream() + ) + ->withStatus(401); + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -916,24 +949,28 @@ public function testShouldReturn401FromAfter() public function testShouldModifyRequestUsingAnonymousBefore() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $test = $request->getAttribute("test"); $response->getBody()->write($test); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "before" => function ($request, $arguments) { - return $request->withAttribute("test", "test"); - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "before" => function ($request, $arguments) { + return $request->withAttribute("test", "test"); + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -944,22 +981,26 @@ public function testShouldModifyRequestUsingAnonymousBefore() public function testShouldModifyRequestUsingInvokableBefore() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $test = $request->getAttribute("test"); $response->getBody()->write($test); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "before" => new TestBeforeHandler - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "before" => new TestBeforeHandler(), + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -970,22 +1011,26 @@ public function testShouldModifyRequestUsingInvokableBefore() public function testShouldModifyRequestUsingArrayNotationBefore() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $test = $request->getAttribute("test"); $response->getBody()->write($test); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "before" => [TestBeforeHandler::class, "before"] - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "before" => [TestBeforeHandler::class, "before"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -996,28 +1041,34 @@ public function testShouldModifyRequestUsingArrayNotationBefore() public function testShouldHandleRulesArrayBug84() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "rules" => [ - new RequestPathRule([ - "path" => ["/api"], - "ignore" => ["/api/login"], - ]), - new RequestMethodRule([ - "ignore" => ["OPTIONS"], - ]) - ], - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "rules" => [ + new RequestPathRule([ + "path" => ["/api"], + "ignore" => ["/api/login"], + ]), + new RequestMethodRule([ + "ignore" => ["OPTIONS"], + ]), + ], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -1025,8 +1076,10 @@ public function testShouldHandleRulesArrayBug84() $this->assertEquals(401, $response->getStatusCode()); $this->assertEquals("", $response->getBody()); - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api/login"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api/login" + ); $response = $collection->dispatch($request, $default); @@ -1036,20 +1089,26 @@ public function testShouldHandleRulesArrayBug84() public function testShouldHandleDefaultPathBug118() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api" + ); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "ignore" => "/api/login", - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "ignore" => ["/api/login"], + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -1057,8 +1116,10 @@ public function testShouldHandleDefaultPathBug118() $this->assertEquals(401, $response->getStatusCode()); $this->assertEquals("", $response->getBody()); - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api/login"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api/login" + ); $response = $collection->dispatch($request, $default); @@ -1068,31 +1129,35 @@ public function testShouldHandleDefaultPathBug118() public function testShouldBindToMiddleware() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/") ->withHeader("Authorization", "Bearer " . self::$acmeToken); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $before = $request->getAttribute("before"); $response->getBody()->write($before); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "before" => function ($request, $arguments) { - $before = get_class($this); - return $request->withAttribute("before", $before); - }, - "after" => function ($response, $arguments) { - $after = get_class($this); - $response->getBody()->write($after); - return $response; - } - - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "before" => function (ServerRequestInterface $request, $arguments) { + $before = get_class($this); + var_dump($before); + return $request->withAttribute("before", $before); + }, + "after" => function ($response, $arguments) { + $after = get_class($this); + $response->getBody()->write($after); + return $response; + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -1103,16 +1168,20 @@ public function testShouldBindToMiddleware() public function testShouldHandlePsr7() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withHeader("X-Token", "Bearer " . self::$acmeToken); - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); - $auth = new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "header" => "X-Token" - ]); + $auth = new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "header" => "X-Token", + ] + ) + ); $next = function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write("Success"); @@ -1127,24 +1196,30 @@ public function testShouldHandlePsr7() public function testShouldHaveUriInErrorHandlerIssue96() { - $request = (new ServerRequestFactory) - ->createServerRequest("GET", "https://example.com/api/foo?bar=pop"); + $request = (new ServerRequestFactory())->createServerRequest( + "GET", + "https://example.com/api/foo?bar=pop" + ); $dummy = null; $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { - $dummy = $arguments["uri"]; - } - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { + $dummy = $arguments["uri"]; + }, + ] + ) + ), ]); $response = $collection->dispatch($request, $default); @@ -1156,22 +1231,26 @@ public function testShouldHaveUriInErrorHandlerIssue96() public function testShouldUseCookieIfHeaderMissingIssue156() { - $request = (new ServerRequestFactory) + $request = (new ServerRequestFactory()) ->createServerRequest("GET", "https://example.com/api") ->withCookieParams(["token" => self::$acmeToken]); $default = function (ServerRequestInterface $request) { - $response = (new ResponseFactory)->createResponse(); + $response = (new ResponseFactory())->createResponse(); $response->getBody()->write("Success"); return $response; }; $collection = new MiddlewareCollection([ - new JwtAuthentication([ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "header" => "X-Token", - "regexp" => "/(.*)/", - ]) + new JwtAuthentication( + new JwtAuthOptions( + ...[ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "header" => "X-Token", + "regexp" => "/(.*)/", + ] + ) + ), ]); $response = $collection->dispatch($request, $default); From 6335382bcca47b6bc0c96d17bddd84aee3b768c4 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 16:52:32 -0300 Subject: [PATCH 04/12] fix: update dependencies --- .phpunit.result.cache | 1 + composer.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..e9768a1 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.032,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0.004,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0.002,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0.008,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0.001,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file diff --git a/composer.json b/composer.json index bd2378b..694e76b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "require": { "php": "^8.0", "psr/log": "^3.0", - "firebase/php-jwt": "^5.0", + "firebase/php-jwt": "^6.0", "psr/http-message": "^1.0", "tuupola/http-factory": "^1.4", "tuupola/callable-handler": "^1.0", From 14896f7db5b5054d4ddc62ba5ce13824bc60569e Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 17:08:45 -0300 Subject: [PATCH 05/12] fix: update rules to use count --- .phpunit.result.cache | 2 +- src/JwtAuthentication.php | 2 +- tests/JwtAuthenticationTest.php | 24 +++++++++++------------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index e9768a1..f1c7b03 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.032,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0.004,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0.002,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0.008,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0.001,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file +{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.033,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0.002,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0.006,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0.001,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index 97ca30d..ca90f34 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -86,7 +86,7 @@ public function __construct(JwtAuthOptions $options, ?LoggerInterface $logger = /* If nothing was passed in options add default rules. */ /* This also means $options->rules overrides $options->path */ /* and $options->ignore */ - if (!(isset($options->rules) && count($options->rules))) { + if (!count($options->rules)) { $this->rules->push(new RequestMethodRule([ "ignore" => ["OPTIONS"] ])); diff --git a/tests/JwtAuthenticationTest.php b/tests/JwtAuthenticationTest.php index 63e0d2a..1e550c9 100644 --- a/tests/JwtAuthenticationTest.php +++ b/tests/JwtAuthenticationTest.php @@ -212,7 +212,7 @@ public function testShouldReturn200WithTokenFromBearerCookie() $this->assertEquals("Success", $response->getBody()); } - + public function testShouldAlterResponseWithAnonymousAfter() { @@ -1055,18 +1055,16 @@ public function testShouldHandleRulesArrayBug84() $collection = new MiddlewareCollection([ new JwtAuthentication( new JwtAuthOptions( - ...[ - "secret" => "supersecretkeyyoushouldnotcommittogithub", - "rules" => [ - new RequestPathRule([ - "path" => ["/api"], - "ignore" => ["/api/login"], - ]), - new RequestMethodRule([ - "ignore" => ["OPTIONS"], - ]), - ], - ] + secret: "supersecretkeyyoushouldnotcommittogithub", + rules: [ + new RequestPathRule([ + "path" => ["/api"], + "ignore" => ["/api/login"], + ]), + new RequestMethodRule([ + "ignore" => ["OPTIONS"], + ]), + ], ) ), ]); From 921271adcfa9ec2bc7450c72e10d8e1f12c6c73f Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 17:16:09 -0300 Subject: [PATCH 06/12] fix: include rules when passed in options object --- .phpunit.result.cache | 2 +- src/JwtAuthentication.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index f1c7b03..2689452 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.033,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0.003,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0.002,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0.006,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0.001,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file +{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.008,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index ca90f34..1ea3648 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -94,6 +94,8 @@ public function __construct(JwtAuthOptions $options, ?LoggerInterface $logger = "path" => $this->options->path, "ignore" => $this->options->ignore ])); + } else { + $this->rules($options->rules); } } From 853e220044899b58e71c32b05aea58ada0f9e318 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Thu, 19 Oct 2023 17:22:41 -0300 Subject: [PATCH 07/12] fix: remove result cache --- .phpunit.result.cache | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 2689452..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":3},"times":{"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithoutToken":0.008,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeader":0.001,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromHeaderWithCustomRegexp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithTokenFromBearerCookie":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithAnonymousAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithInvokableAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAlterResponseWithArrayNotationAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401WithInvalidAlgorithm":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithOptions":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithInvalidToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn400WithExpiredToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithPath":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn200WithoutTokenWithIgnore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldNotAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowInsecure":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInLocalhost":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldRelaxInsecureInExampleCom":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAttachCustomToken":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAfterWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallBeforeWithProperArguments":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallAnonymousErrorFunction":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallInvokableErrorClass":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallArrayNotationError":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldCallErrorAndModifyBody":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldAllowUnauthenticatedHttp":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldReturn401FromAfter":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingAnonymousBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingInvokableBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldModifyRequestUsingArrayNotationBefore":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleRulesArrayBug84":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandleDefaultPathBug118":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldBindToMiddleware":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHandlePsr7":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldHaveUriInErrorHandlerIssue96":0,"Tuupola\\Middleware\\JwtAuthenticationTest::testShouldUseCookieIfHeaderMissingIssue156":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldNotAuthenticateOptions":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticatePost":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldAuthenticateGet":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestMethodTest::testShouldConfigureIgnore":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAcceptArrayAndStringAsPath":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateEverything":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateOnlyApi":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldIgnoreLogin":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateCreateAndList":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testShouldAuthenticateRegexp":0,"Tuupola\\Middleware\\JwtAuthentication\\RequestPathTest::testBug50ShouldAuthenticateMultipleSlashes":0}} \ No newline at end of file From 1660420dff4c9336d43cde4498eac581e91cf0da Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Mon, 23 Oct 2023 10:43:52 -0300 Subject: [PATCH 08/12] fix: remove semicolon --- src/JwtAuthentication.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index 1ea3648..70cee24 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -248,7 +248,6 @@ private function fetchToken(ServerRequestInterface $request): string } return $cookieParams[$this->options->cookie]; } - ; /* If everything fails log and throw. */ $this->log(LogLevel::WARNING, "Token not found"); From c8831c75b40ea89cf57dfde1331a28319421b61b Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Mon, 23 Oct 2023 11:08:33 -0300 Subject: [PATCH 09/12] fix: lower php version --- .gitignore | 1 + .vscode/settings.json | 2 +- src/JwtAuthentication.php | 4 ++-- src/JwtAuthentication/JwtAuthOptions.php | 9 ++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 49b4f12..e2de534 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock .phplint-cache coverage.xml +*.cache diff --git a/.vscode/settings.json b/.vscode/settings.json index 2af03ac..a223541 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "php.version": "8.1" + "php.version": "7.4" } \ No newline at end of file diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index 70cee24..d15a340 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -261,8 +261,8 @@ private function decodeToken(string $token): array $algo = $this->options->algorithm; $key = new Key( - keyMaterial: $this->options->secret, - algorithm: $algo + $this->options->secret, + $algo ); $decoded = JWT::decode( diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index 3f1d9b0..16d56e4 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -45,7 +45,8 @@ */ class JwtAuthOptions { - public string $secret; + /** @var string|array|ArrayAccessImpl */ + public $secret; public bool $secure; @@ -71,7 +72,8 @@ class JwtAuthOptions private JwtAuthentication $jwtAuthentication; public function __construct( - string|array|ArrayAccessImpl $secret, + /** @var string|array|ArrayAccessImpl */ + $secret, bool $secure = true, array $relaxed = ["localhost", "127.0.0.1"], string $algorithm = "HS256", @@ -102,13 +104,14 @@ public function __construct( $this->error = $error; } - private function checkSecret($secret): string|array + private function checkSecret($secret): string|array|\ArrayAccess { if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { throw new InvalidArgumentException( 'Secret must be either a string or an array of "kid" => "secret" pairs' ); } + return $secret; } From b2b0594fe663cad23c8480c59f6828a803dc4607 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Mon, 23 Oct 2023 13:26:21 -0300 Subject: [PATCH 10/12] fix: support multi secret key --- src/JwtAuthentication.php | 33 +++++++++++++++++------ src/JwtAuthentication/JwtAuthOptions.php | 34 +++++++++++++++++++----- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index d15a340..d602016 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -257,17 +257,16 @@ private function fetchToken(ServerRequestInterface $request): string private function decodeToken(string $token): array { - try { - $algo = $this->options->algorithm; - - $key = new Key( - $this->options->secret, - $algo - ); + $keys = $this->createKeysFromAlgorithms(); + + if (count($keys) === 1) { + $keys = current($keys); + } + try { $decoded = JWT::decode( $token, - $key + $keys ); return (array) $decoded; @@ -278,6 +277,24 @@ private function decodeToken(string $token): array } } + /** + * @return array + */ + private function createKeysFromAlgorithms(): array + { + $keyObjects = []; + + foreach ($this->options->algorithm as $kid => $algorithm) { + $keyId = !is_numeric($kid) ? $kid : $algorithm; + + $secret = $this->options->secret[$kid]; + + $keyObjects[$keyId] = new Key($secret, $algorithm); + } + + return $keyObjects; + } + /** * Logs with an arbitrary level. * diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index 16d56e4..e3ce0ea 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -45,14 +45,16 @@ */ class JwtAuthOptions { - /** @var string|array|ArrayAccessImpl */ + /** @var array|ArrayAccessImpl */ public $secret; + /** @var array|ArrayAccessImpl */ + public $algorithm; public bool $secure; /** @var array */ public array $relaxed; - public string $algorithm; + public string $header; public string $regexp; public string $cookie; @@ -74,9 +76,10 @@ class JwtAuthOptions public function __construct( /** @var string|array|ArrayAccessImpl */ $secret, + /** @var string|array|ArrayAccessImpl */ + $algorithm = "HS256", bool $secure = true, array $relaxed = ["localhost", "127.0.0.1"], - string $algorithm = "HS256", string $header = "Authorization", string $regexp = "/Bearer\s+(.*)$/i", string $cookie = "token", @@ -89,9 +92,9 @@ public function __construct( ?callable $error = null ) { $this->secret = $this->checkSecret($secret); + $this->algorithm = $this->applyAlgorithm($this->secret, $algorithm); $this->secure = $secure; $this->relaxed = $relaxed; - $this->algorithm = $algorithm; $this->header = $header; $this->regexp = $regexp; $this->cookie = $cookie; @@ -104,7 +107,7 @@ public function __construct( $this->error = $error; } - private function checkSecret($secret): string|array|\ArrayAccess + private function checkSecret($secret): array|\ArrayAccess { if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { throw new InvalidArgumentException( @@ -112,7 +115,26 @@ private function checkSecret($secret): string|array|\ArrayAccess ); } - return $secret; + return (array) $secret; + } + + private function applyAlgorithm($secret, $algorithm) + { + if (is_string($algorithm)) { + $secretIndex = array_keys((array) $secret); + + return array_fill_keys($secretIndex, $algorithm); + } + + foreach ($secret as $key => $value) { + if (in_array($key, $algorithm)) { + throw new InvalidArgumentException( + "Al secrets must have a corresponding algorithm" + ); + } + } + + return $algorithm; } private function bindClosure(?callable $closure, JwtAuthentication $target): ?\Closure From a64b9d4aa62ca26f31de3c873022924e4ca7e35a Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Mon, 23 Oct 2023 13:54:03 -0300 Subject: [PATCH 11/12] fix: add possibility to instantiate options through factory method --- src/JwtAuthentication/JwtAuthOptions.php | 104 ++++++++------ tests/JwtAuthenticationTest.php | 166 ++++++++++++----------- 2 files changed, 149 insertions(+), 121 deletions(-) diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index e3ce0ea..aa16825 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -107,47 +107,31 @@ public function __construct( $this->error = $error; } - private function checkSecret($secret): array|\ArrayAccess + public static function fromArray(array $data): self { - if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { - throw new InvalidArgumentException( - 'Secret must be either a string or an array of "kid" => "secret" pairs' - ); + $values = [ + "secret" => "", + "algorithm" => "HS256", + "secure" => true, + "relaxed" => ["localhost", "127.0.0.1"], + "header" => "Authorization", + "regexp" => "/Bearer\s+(.*)$/i", + "cookie" => "token", + "attribute" => "token", + "path" => ["/"], + "ignore" => [], + "rules" => [], + "before" => null, + "after" => null, + "error" => null + ]; + $inArray = []; + + foreach ($values as $key => $value) { + $inArray = $data[$key] ?? $value; } - return (array) $secret; - } - - private function applyAlgorithm($secret, $algorithm) - { - if (is_string($algorithm)) { - $secretIndex = array_keys((array) $secret); - - return array_fill_keys($secretIndex, $algorithm); - } - - foreach ($secret as $key => $value) { - if (in_array($key, $algorithm)) { - throw new InvalidArgumentException( - "Al secrets must have a corresponding algorithm" - ); - } - } - - return $algorithm; - } - - private function bindClosure(?callable $closure, JwtAuthentication $target): ?\Closure - { - if ($closure) { - if ($closure instanceof \Closure) { - return $closure->bindTo($target); - } - - return \Closure::fromCallable($closure); - } - - return null; + return new self(...$inArray); } public function bindToAuthentication(JwtAuthentication $target): self @@ -174,7 +158,6 @@ public function onError(ResponseInterface $response, array $arguments): ?Respons /** * Set the before handler. */ - public function onBeforeCallable(ServerRequestInterface $request, array $params): ?ServerRequestInterface { $func = $this->before; @@ -190,4 +173,47 @@ public function onAfterCallable(ResponseInterface $response, array $params): ?Re return is_null($func) ? null : $func($response, $params); } + + private function checkSecret($secret): array|\ArrayAccess + { + if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { + throw new InvalidArgumentException( + 'Secret must be either a string or an array of "kid" => "secret" pairs' + ); + } + + return (array) $secret; + } + + private function applyAlgorithm($secret, $algorithm) + { + if (is_string($algorithm)) { + $secretIndex = array_keys((array) $secret); + + return array_fill_keys($secretIndex, $algorithm); + } + + foreach ($secret as $key => $value) { + if (in_array($key, $algorithm)) { + throw new InvalidArgumentException( + "Al secrets must have a corresponding algorithm" + ); + } + } + + return $algorithm; + } + + private function bindClosure(?callable $closure, JwtAuthentication $target): ?\Closure + { + if ($closure) { + if ($closure instanceof \Closure) { + return $closure->bindTo($target); + } + + return \Closure::fromCallable($closure); + } + + return null; + } } diff --git a/tests/JwtAuthenticationTest.php b/tests/JwtAuthenticationTest.php index 1e550c9..0676f2e 100644 --- a/tests/JwtAuthenticationTest.php +++ b/tests/JwtAuthenticationTest.php @@ -83,7 +83,7 @@ public function testShouldReturn401WithoutToken() return $response; }; $options = new JwtAuthOptions( - secret: "supersecretkeyyoushouldnotcommittogithub" + "supersecretkeyyoushouldnotcommittogithub" ); $collection = new MiddlewareCollection([ new JwtAuthentication($options), @@ -107,8 +107,8 @@ public function testShouldReturn200WithTokenFromHeader() return $response; }; - $options = new JwtAuthOptions( - ...[ + $options = JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "header" => "X-Token", ] @@ -136,8 +136,8 @@ public function testShouldReturn200WithTokenFromHeaderWithCustomRegexp() return $response; }; - $options = new JwtAuthOptions( - ...[ + $options = JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "header" => "X-Token", "regexp" => "/(.*)/", @@ -166,8 +166,8 @@ public function testShouldReturn200WithTokenFromCookie() return $response; }; - $options = new JwtAuthOptions( - ...[ + $options = JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "cookie" => "nekot", ] @@ -195,8 +195,8 @@ public function testShouldReturn200WithTokenFromBearerCookie() return $response; }; - $options = new JwtAuthOptions( - ...[ + $options = JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "cookie" => "nekot", ] @@ -228,8 +228,8 @@ public function testShouldAlterResponseWithAnonymousAfter() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "after" => function ($response, $arguments) { return $response->withHeader( @@ -265,8 +265,8 @@ public function testShouldAlterResponseWithInvokableAfter() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "after" => new TestAfterHandler(), ] @@ -297,8 +297,8 @@ public function testShouldAlterResponseWithArrayNotationAfter() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "after" => [TestAfterHandler::class, "after"], ] @@ -329,8 +329,8 @@ public function testShouldReturn401WithInvalidAlgorithm() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "algorithm" => "nosuch", ] @@ -358,8 +358,8 @@ public function testShouldReturn200WithOptions() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -386,8 +386,8 @@ public function testShouldReturn400WithInvalidToken() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -414,8 +414,8 @@ public function testShouldReturn400WithExpiredToken() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -443,8 +443,8 @@ public function testShouldReturn200WithoutTokenWithPath() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "path" => ["/api", "/foo"], "secret" => "supersecretkeyyoushouldnotcommittogithub", ] @@ -473,8 +473,8 @@ public function testShouldReturn200WithoutTokenWithIgnore() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "path" => ["/api", "/foo"], "ignore" => ["/api/ping"], "secret" => "supersecretkeyyoushouldnotcommittogithub", @@ -505,8 +505,8 @@ public function testShouldNotAllowInsecure() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -530,8 +530,8 @@ public function testShouldAllowInsecure() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "secure" => false, ] @@ -559,8 +559,8 @@ public function testShouldRelaxInsecureInLocalhost() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -587,8 +587,8 @@ public function testShouldRelaxInsecureInExampleCom() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "relaxed" => ["example.com"], ] @@ -619,8 +619,8 @@ public function testShouldAttachToken() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", ] ) @@ -650,8 +650,8 @@ public function testShouldAttachCustomToken() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "attribute" => "nekot", ] @@ -682,8 +682,8 @@ public function testShouldCallAfterWithProperArguments() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "after" => function ($response, $arguments) use (&$decoded, &$token) { $decoded = $arguments["decoded"]; @@ -719,8 +719,8 @@ public function testShouldCallBeforeWithProperArguments() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "before" => function ($response, $arguments) use (&$decoded, &$token) { $decoded = $arguments["decoded"]; @@ -754,8 +754,8 @@ public function testShouldCallAnonymousErrorFunction() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommit", "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { $response->getBody()->write("error"); @@ -796,8 +796,8 @@ public function testShouldCallInvokableErrorClass() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommit", "error" => new TestErrorHandler(), ] @@ -829,8 +829,8 @@ public function testShouldCallArrayNotationError() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommit", "error" => [TestErrorHandler::class, "error"], ] @@ -862,8 +862,8 @@ public function testShouldCallErrorAndModifyBody() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { $dummy = true; @@ -897,8 +897,8 @@ public function testShouldAllowUnauthenticatedHttp() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "path" => ["/api", "/bar"], ] @@ -926,8 +926,8 @@ public function testShouldReturn401FromAfter() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "after" => function ($response, $arguments) { return $response @@ -962,8 +962,8 @@ public function testShouldModifyRequestUsingAnonymousBefore() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "before" => function ($request, $arguments) { return $request->withAttribute("test", "test"); @@ -994,8 +994,8 @@ public function testShouldModifyRequestUsingInvokableBefore() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "before" => new TestBeforeHandler(), ] @@ -1024,8 +1024,8 @@ public function testShouldModifyRequestUsingArrayNotationBefore() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "before" => [TestBeforeHandler::class, "before"], ] @@ -1054,17 +1054,19 @@ public function testShouldHandleRulesArrayBug84() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - secret: "supersecretkeyyoushouldnotcommittogithub", - rules: [ - new RequestPathRule([ - "path" => ["/api"], - "ignore" => ["/api/login"], - ]), - new RequestMethodRule([ - "ignore" => ["OPTIONS"], - ]), - ], + JwtAuthOptions::fromArray( + [ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "rules" => [ + new RequestPathRule([ + "path" => ["/api"], + "ignore" => ["/api/login"], + ]), + new RequestMethodRule([ + "ignore" => ["OPTIONS"], + ]), + ], + ] ) ), ]); @@ -1100,8 +1102,8 @@ public function testShouldHandleDefaultPathBug118() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "ignore" => ["/api/login"], ] @@ -1140,8 +1142,8 @@ public function testShouldBindToMiddleware() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "before" => function (ServerRequestInterface $request, $arguments) { $before = get_class($this); @@ -1173,8 +1175,8 @@ public function testShouldHandlePsr7() $response = (new ResponseFactory())->createResponse(); $auth = new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "header" => "X-Token", ] @@ -1209,8 +1211,8 @@ public function testShouldHaveUriInErrorHandlerIssue96() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "error" => function (ResponseInterface $response, $arguments) use (&$dummy) { $dummy = $arguments["uri"]; @@ -1241,8 +1243,8 @@ public function testShouldUseCookieIfHeaderMissingIssue156() $collection = new MiddlewareCollection([ new JwtAuthentication( - new JwtAuthOptions( - ...[ + JwtAuthOptions::fromArray( + [ "secret" => "supersecretkeyyoushouldnotcommittogithub", "header" => "X-Token", "regexp" => "/(.*)/", From eabb0e7ea73350c836f1ce0b50b5be96554eb229 Mon Sep 17 00:00:00 2001 From: Gabriel Berthier Date: Mon, 23 Oct 2023 15:41:33 -0300 Subject: [PATCH 12/12] fix: return multi secret options --- src/JwtAuthentication.php | 4 +- src/JwtAuthentication/JwtAuthOptions.php | 12 +-- tests/JwtAuthenticationTest.php | 116 +++++++++++++++++++++++ 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index d602016..d83fbf6 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -258,7 +258,7 @@ private function fetchToken(ServerRequestInterface $request): string private function decodeToken(string $token): array { $keys = $this->createKeysFromAlgorithms(); - + if (count($keys) === 1) { $keys = current($keys); } @@ -285,7 +285,7 @@ private function createKeysFromAlgorithms(): array $keyObjects = []; foreach ($this->options->algorithm as $kid => $algorithm) { - $keyId = !is_numeric($kid) ? $kid : $algorithm; + $keyId = is_numeric($kid) ? $algorithm : $kid; $secret = $this->options->secret[$kid]; diff --git a/src/JwtAuthentication/JwtAuthOptions.php b/src/JwtAuthentication/JwtAuthOptions.php index aa16825..ce9f7c3 100644 --- a/src/JwtAuthentication/JwtAuthOptions.php +++ b/src/JwtAuthentication/JwtAuthOptions.php @@ -128,7 +128,7 @@ public static function fromArray(array $data): self $inArray = []; foreach ($values as $key => $value) { - $inArray = $data[$key] ?? $value; + $inArray[$key] = $data[$key] ?? $value; } return new self(...$inArray); @@ -174,7 +174,7 @@ public function onAfterCallable(ResponseInterface $response, array $params): ?Re return is_null($func) ? null : $func($response, $params); } - private function checkSecret($secret): array|\ArrayAccess + private function checkSecret($secret): array { if (!(is_array($secret) || is_string($secret) || $secret instanceof \ArrayAccess)) { throw new InvalidArgumentException( @@ -185,18 +185,18 @@ private function checkSecret($secret): array|\ArrayAccess return (array) $secret; } - private function applyAlgorithm($secret, $algorithm) + private function applyAlgorithm(array $secret, $algorithm) { if (is_string($algorithm)) { - $secretIndex = array_keys((array) $secret); + $secretIndex = array_keys($secret); return array_fill_keys($secretIndex, $algorithm); } foreach ($secret as $key => $value) { - if (in_array($key, $algorithm)) { + if (!in_array($key, $algorithm)) { throw new InvalidArgumentException( - "Al secrets must have a corresponding algorithm" + "All secrets must have a corresponding algorithm" ); } } diff --git a/tests/JwtAuthenticationTest.php b/tests/JwtAuthenticationTest.php index 0676f2e..c5d00db 100644 --- a/tests/JwtAuthenticationTest.php +++ b/tests/JwtAuthenticationTest.php @@ -212,7 +212,123 @@ public function testShouldReturn200WithTokenFromBearerCookie() $this->assertEquals("Success", $response->getBody()); } + public function testShouldReturn200WithSecretArray() + { + $request = (new ServerRequestFactory) + ->createServerRequest("GET", "https://example.com/api") + ->withHeader("Authorization", "Bearer " . self::$betaToken); + + $default = function (ServerRequestInterface $request) { + $response = (new ResponseFactory)->createResponse(); + $response->getBody()->write("Success"); + return $response; + }; + + $options = JwtAuthOptions::fromArray( + [ + "secret" => [ + "acme" => "supersecretkeyyoushouldnotcommittogithub", + "beta" => "anothersecretkeyfornevertocommittogithub" + ] + ] + ); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options) + ]); + + $response = $collection->dispatch($request, $default); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals("Success", $response->getBody()); + } + + public function testShouldReturn401WithSecretArray() + { + $request = (new ServerRequestFactory) + ->createServerRequest("GET", "https://example.com/api") + ->withHeader("Authorization", "Bearer " . self::$betaToken); + + $default = function (ServerRequestInterface $request) { + $response = (new ResponseFactory)->createResponse(); + $response->getBody()->write("Success"); + return $response; + }; + + $collection = new MiddlewareCollection([ + new JwtAuthentication( + JwtAuthOptions::fromArray( + [ + "secret" => [ + "xxxx" => "supersecretkeyyoushouldnotcommittogithub", + "yyyy" => "anothersecretkeyfornevertocommittogithub" + ] + ] + ) + ) + ]); + + $response = $collection->dispatch($request, $default); + $this->assertEquals(401, $response->getStatusCode()); + $this->assertEquals("", $response->getBody()); + } + + public function testShouldReturn200WithSecretArrayAccess() + { + $request = (new ServerRequestFactory) + ->createServerRequest("GET", "https://example.com/api") + ->withHeader("Authorization", "Bearer " . self::$betaToken); + + $default = function (ServerRequestInterface $request) { + $response = (new ResponseFactory)->createResponse(); + $response->getBody()->write("Success"); + return $response; + }; + + $secret = new \ArrayObject(); + $secret["acme"] = "supersecretkeyyoushouldnotcommittogithub"; + $secret["beta"] = "anothersecretkeyfornevertocommittogithub"; + + $options = JwtAuthOptions::fromArray([ + "secret" => $secret + ]); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options) + ]); + + $response = $collection->dispatch($request, $default); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals("Success", $response->getBody()); + } + + public function testShouldReturn401WithSecretArrayAccess() + { + $request = (new ServerRequestFactory) + ->createServerRequest("GET", "https://example.com/api") + ->withHeader("Authorization", "Bearer " . self::$betaToken); + + $default = function (ServerRequestInterface $request) { + $response = (new ResponseFactory)->createResponse(); + $response->getBody()->write("Success"); + return $response; + }; + $secret = new \ArrayObject(); + $secret["xxxx"] = "supersecretkeyyoushouldnotcommittogithub"; + $secret["yyyy"] = "anothersecretkeyfornevertocommittogithub"; + + $options = JwtAuthOptions::fromArray([ + "secret" => $secret + ]); + + $collection = new MiddlewareCollection([ + new JwtAuthentication($options) + ]); + + $response = $collection->dispatch($request, $default); + $this->assertEquals(401, $response->getStatusCode()); + $this->assertEquals("", $response->getBody()); + } public function testShouldAlterResponseWithAnonymousAfter() {