diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff30dc..8c5ce4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 3.2.2 under development +- Enh #101: Refactor `Authentication` middleware authentication failure handling (@olegbaturin) +- Chg #101: Change PHP constraint in composer.json to 8.1 - 8.4 (@olegbaturin) - Chg #104: Bump minimal PHP version to 8.1 (@vjik) - Enh #104: Explicitly mark readonly properties (@vjik) diff --git a/composer.json b/composer.json index 2f8e73b..d6041cf 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "roave/infection-static-analysis-plugin": "^1.35", "spatie/phpunit-watcher": "^1.24.4", "vimeo/psalm": "^5.26.1 || ^6.10", + "yiisoft/di": "^1.4", "yiisoft/yii-debug": "dev-master" }, "autoload": { @@ -61,6 +62,7 @@ "source-directory": "config" }, "config-plugin": { + "di-web": "di-web.php", "params": "params.php" } }, diff --git a/config/di-web.php b/config/di-web.php new file mode 100644 index 0000000..f4b5931 --- /dev/null +++ b/config/di-web.php @@ -0,0 +1,14 @@ + AuthenticationFailureHandler::class, +]; diff --git a/src/Handler/AuthenticationFailureHandler.php b/src/AuthenticationFailureHandler.php similarity index 82% rename from src/Handler/AuthenticationFailureHandler.php rename to src/AuthenticationFailureHandler.php index d98da41..620d776 100644 --- a/src/Handler/AuthenticationFailureHandler.php +++ b/src/AuthenticationFailureHandler.php @@ -2,18 +2,17 @@ declare(strict_types=1); -namespace Yiisoft\Auth\Handler; +namespace Yiisoft\Auth; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\RequestHandlerInterface; use Yiisoft\Http\Status; /** * Default authentication failure handler. Responds with "401 Unauthorized" HTTP status code. */ -final class AuthenticationFailureHandler implements RequestHandlerInterface +final class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface { public function __construct( private readonly ResponseFactoryInterface $responseFactory, diff --git a/src/AuthenticationFailureHandlerInterface.php b/src/AuthenticationFailureHandlerInterface.php new file mode 100644 index 0000000..e9aba10 --- /dev/null +++ b/src/AuthenticationFailureHandlerInterface.php @@ -0,0 +1,16 @@ +failureHandler = $authenticationFailureHandler ?? new AuthenticationFailureHandler( - $responseFactory - ); } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface diff --git a/tests/AuthenticationFailureHandlerTest.php b/tests/AuthenticationFailureHandlerTest.php index b9d8f61..353a34f 100644 --- a/tests/AuthenticationFailureHandlerTest.php +++ b/tests/AuthenticationFailureHandlerTest.php @@ -8,7 +8,7 @@ use Nyholm\Psr7\ServerRequest; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Yiisoft\Auth\Handler\AuthenticationFailureHandler; +use Yiisoft\Auth\AuthenticationFailureHandler; use Yiisoft\Http\Method; use Yiisoft\Http\Status; diff --git a/tests/AuthenticationMiddlewareTest.php b/tests/AuthenticationMiddlewareTest.php index e193dc3..19f6664 100644 --- a/tests/AuthenticationMiddlewareTest.php +++ b/tests/AuthenticationMiddlewareTest.php @@ -13,6 +13,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; +use Yiisoft\Auth\AuthenticationFailureHandlerInterface; use Yiisoft\Auth\AuthenticationMethodInterface; use Yiisoft\Auth\IdentityInterface; use Yiisoft\Auth\Middleware\Authentication; @@ -53,7 +54,7 @@ function (ServerRequestInterface $request) use ($identity) { } ); - $auth = new Authentication($this->authenticationMethod, $this->responseFactory); + $auth = new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler()); $auth->process($request, $handler); } @@ -80,7 +81,7 @@ public function testShouldSkipCheckForOptionalPath(string $path): void ->expects($this->once()) ->method('handle'); - $auth = (new Authentication($this->authenticationMethod, $this->responseFactory)) + $auth = (new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler())) ->withOptionalPatterns([$path]); $auth->process($request, $handler); } @@ -108,7 +109,7 @@ public function testShouldNotExecuteHandlerAndReturn401OnAuthenticationFailure() ->expects($this->never()) ->method('handle'); - $auth = new Authentication($this->authenticationMethod, $this->responseFactory); + $auth = new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler()); $response = $auth->process($request, $handler); $this->assertEquals(401, $response->getStatusCode()); $this->assertEquals($headerValue, $response->getHeaderLine($header)); @@ -137,34 +138,33 @@ public function testCustomAuthenticationFailureResponse(): void ->expects($this->never()) ->method('handle'); - $failureResponse = 'test custom response'; + $failureResponseBody = 'test custom response'; $auth = new Authentication( $this->authenticationMethod, - $this->responseFactory, - $this->createAuthenticationFailureHandler($failureResponse) + $this->createAuthenticationFailureHandler($failureResponseBody) ); $response = $auth->process($request, $handler); $this->assertEquals(401, $response->getStatusCode()); $this->assertEquals($headerValue, $response->getHeaderLine($header)); - $this->assertEquals($failureResponse, (string)$response->getBody()); + $this->assertEquals($failureResponseBody, (string)$response->getBody()); } public function testImmutability(): void { $original = new Authentication( $this->authenticationMethod, - $this->responseFactory + $this->createAuthenticationFailureHandler() ); $this->assertNotSame($original, $original->withOptionalPatterns(['test'])); } - private function createAuthenticationFailureHandler(string $failureResponse): RequestHandlerInterface + private function createAuthenticationFailureHandler(string $failureResponseBody = 'Authentication failed'): AuthenticationFailureHandlerInterface { - return new class ($failureResponse, new Psr17Factory()) implements RequestHandlerInterface { + return new class ($failureResponseBody, new Psr17Factory()) implements AuthenticationFailureHandlerInterface { public function __construct( - private readonly string $failureResponse, + private readonly string $failureResponseBody, private readonly ResponseFactoryInterface $responseFactory, ) { } @@ -174,7 +174,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $response = $this->responseFactory->createResponse(Status::UNAUTHORIZED); $response ->getBody() - ->write($this->failureResponse); + ->write($this->failureResponseBody); return $response; } }; diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php new file mode 100644 index 0000000..c98feca --- /dev/null +++ b/tests/ConfigTest.php @@ -0,0 +1,41 @@ +createContainer(); + + $failureHandler = $container->get(AuthenticationFailureHandlerInterface::class); + $this->assertInstanceOf(AuthenticationFailureHandler::class, $failureHandler); + } + + private function createContainer(): Container + { + return new Container( + ContainerConfig::create()->withDefinitions([ + ResponseFactoryInterface::class => Psr17Factory::class, + ...$this->getContainerDefinitions(), + ]) + ); + } + + private function getContainerDefinitions(): array + { + return require dirname(__DIR__) . '/config/di-web.php'; + } +}