From ae801445fa6f7dee7c161e6dad2f395aa3aea04c Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 26 Apr 2023 15:59:15 +0300 Subject: [PATCH] [spiral/auth-http] Adding TokenStorageScope (#931) --- CHANGELOG.md | 8 ++- src/Auth/src/TokenStorageInterface.php | 2 - .../src/Middleware/AuthMiddleware.php | 5 +- src/Framework/Auth/TokenStorageScope.php | 61 +++++++++++++++++ .../Auth/{ => Config}/AuthConfigTest.php | 2 +- .../Framework/Auth/TokenStorageScopeTest.php | 68 +++++++++++++++++++ .../Middleware/AuthMiddlewareTest.php | 47 +++++++++++++ 7 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 src/Framework/Auth/TokenStorageScope.php rename tests/Framework/Auth/{ => Config}/AuthConfigTest.php (98%) create mode 100644 tests/Framework/Auth/TokenStorageScopeTest.php create mode 100644 tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e16d23b..9ef600428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # CHANGELOG ## Unreleased +- **Medium Impact Changes** + - [spiral/scaffolder] Method `baseDirectory` of `Spiral\Scaffolder\Config\ScaffolderConfig` class is deprecated. - **Other Features** + - Added `Spiral\Auth\TokenStorageScope`, this class can be used to get the concrete implementation of + the token storage in a current container scope. + - [spiral/auth-http] Added a `Spiral\Auth\TokenStorageInterface` binding in the `Spiral\Auth\Middleware\AuthMiddleware` + with the used TokenStorage. - [spiral/scaffolder] Added new public method `declarationDirectory` to the `Spiral\Scaffolder\Config\ScaffolderConfig` class that returns the directory path of the specified declaration, or default directory path if not specified. -- **Medium Impact Changes** - - [spiral/scaffolder] Method `baseDirectory` of `Spiral\Scaffolder\Config\ScaffolderConfig` class is deprecated. ## 3.7.1 - 2023-04-21 - **Bug Fixes** diff --git a/src/Auth/src/TokenStorageInterface.php b/src/Auth/src/TokenStorageInterface.php index 09999970e..de823dae9 100644 --- a/src/Auth/src/TokenStorageInterface.php +++ b/src/Auth/src/TokenStorageInterface.php @@ -14,7 +14,6 @@ interface TokenStorageInterface /** * Load token by id, must return null if token not found. * - * * @throws TokenStorageException */ public function load(string $id): ?TokenInterface; @@ -31,7 +30,6 @@ public function create(array $payload, \DateTimeInterface $expiresAt = null): To /** * Delete token from the persistent storage. * - * * @throws TokenStorageException */ public function delete(TokenInterface $token): void; diff --git a/src/AuthHttp/src/Middleware/AuthMiddleware.php b/src/AuthHttp/src/Middleware/AuthMiddleware.php index 06366046e..73c5898bb 100644 --- a/src/AuthHttp/src/Middleware/AuthMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthMiddleware.php @@ -40,7 +40,10 @@ public function process(Request $request, RequestHandlerInterface $handler): Res $authContext = $this->initContext($request, new AuthContext($this->actorProvider, $this->eventDispatcher)); $response = $this->scope->runScope( - [AuthContextInterface::class => $authContext], + [ + AuthContextInterface::class => $authContext, + TokenStorageInterface::class => $this->tokenStorage, + ], static fn () => $handler->handle($request->withAttribute(self::ATTRIBUTE, $authContext)) ); diff --git a/src/Framework/Auth/TokenStorageScope.php b/src/Framework/Auth/TokenStorageScope.php new file mode 100644 index 000000000..ad164e81b --- /dev/null +++ b/src/Framework/Auth/TokenStorageScope.php @@ -0,0 +1,61 @@ +getTokenStorage()->load($id); + } + + /** + * Create token based on the payload provided by actor provider. + * + * @throws TokenStorageException + */ + public function create(array $payload, \DateTimeInterface $expiresAt = null): TokenInterface + { + return $this->getTokenStorage()->create($payload, $expiresAt); + } + + /** + * Delete token from the persistent storage. + * + * @throws TokenStorageException + */ + public function delete(TokenInterface $token): void + { + $this->getTokenStorage()->delete($token); + } + + /** + * @throws ScopeException + */ + private function getTokenStorage(): TokenStorageInterface + { + try { + return $this->container->get(TokenStorageInterface::class); + } catch (NotFoundExceptionInterface $e) { + throw new ScopeException('Unable to resolve token storage, invalid scope', $e->getCode(), $e); + } + } +} diff --git a/tests/Framework/Auth/AuthConfigTest.php b/tests/Framework/Auth/Config/AuthConfigTest.php similarity index 98% rename from tests/Framework/Auth/AuthConfigTest.php rename to tests/Framework/Auth/Config/AuthConfigTest.php index 2b7eaed0d..88cf93592 100644 --- a/tests/Framework/Auth/AuthConfigTest.php +++ b/tests/Framework/Auth/Config/AuthConfigTest.php @@ -1,6 +1,6 @@ createMock(TokenStorageInterface::class); + $storage + ->expects($this->once()) + ->method('load') + ->with('foo') + ->willReturn($token = $this->createMock(TokenInterface::class)); + + $container = new Container(); + $container->bind(TokenStorageInterface::class, $storage); + + $scope = new TokenStorageScope($container); + + $this->assertSame($token, $scope->load('foo')); + } + + public function testCreate(): void + { + $expiresAt = new \DateTimeImmutable(); + + $storage = $this->createMock(TokenStorageInterface::class); + $storage + ->expects($this->once()) + ->method('create') + ->with(['foo' => 'bar'], $expiresAt) + ->willReturn($token = $this->createMock(TokenInterface::class)); + + $container = new Container(); + $container->bind(TokenStorageInterface::class, $storage); + + $scope = new TokenStorageScope($container); + + $this->assertSame($token, $scope->create(['foo' => 'bar'], $expiresAt)); + } + + public function testDelete(): void + { + $token = $this->createMock(TokenInterface::class); + + $storage = $this->createMock(TokenStorageInterface::class); + $storage + ->expects($this->once()) + ->method('delete') + ->with($token); + + $container = new Container(); + $container->bind(TokenStorageInterface::class, $storage); + + $scope = new TokenStorageScope($container); + + $scope->delete($token); + } +} diff --git a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php new file mode 100644 index 000000000..581e810d9 --- /dev/null +++ b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php @@ -0,0 +1,47 @@ +enableMiddlewares(); + } + + public function testTokenStorageInterfaceShouldBeBound(): void + { + $storage = $this->createMock(TokenStorageInterface::class); + $this->getContainer()->bind( + AuthMiddleware::class, + new Autowire(AuthMiddleware::class, ['tokenStorage' => $storage]) + ); + $this->setHttpHandler(function () use ($storage): void { + $scope = $this->getContainer()->get(TokenStorageScope::class); + $ref = new \ReflectionMethod($scope, 'getTokenStorage'); + + $this->assertInstanceOf($storage::class, $ref->invoke($scope)); + $this->assertSame($storage, $ref->invoke($scope)); + }); + + $scope = $this->getContainer()->get(TokenStorageScope::class); + $ref = new \ReflectionMethod($scope, 'getTokenStorage'); + $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); + + $this->getHttp()->get('/'); + + $scope = $this->getContainer()->get(TokenStorageScope::class); + $ref = new \ReflectionMethod($scope, 'getTokenStorage'); + $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); + } +}