Keep configured auth transports when faking an actor
withActor() replaced the whole TransportRegistry with a fresh one holding a
single transport named "testing". AuthTransportMiddleware, which is how
HttpAuthBootloader wires auth into an application, asks the registry for a
transport by name, so a request through it died with
Spiral\Auth\Exception\TransportException: Undefined auth transport header
AuthMiddleware kept working because it iterates over every transport, which is
why the failure looked like it only happened with a custom transport.
The fake registry is now built from the names the application has configured,
each mapped to the same fake HeaderTransport that reads X-Test-Token, so
whichever name the middleware asks for it gets a transport that finds the test
token. "testing" stays and becomes the default. An application with no registry
bound behaves as before.
The test app had no withActor() coverage at all, so the three tests come with
two middleware stubs: one that runs AuthTransportMiddleware('header') resolved
from the http scope, one that reports the actor into the response body.
For spiral/framework#1231
This commit is contained in:
1 parent
20591647eb
commit
86d47b17ae
4 files changed
+144
-3
No files matched your search
+26
-3
@@ -421,9 +421,7 @@ class FakeHttp
|
||||
$bindings[ActorProviderInterface::class] = new FakeActorProvider($this->actor);
|
||||
$bindings[TokenStorageInterface::class] = new FakeTokenStorage();
|
||||
|
||||
$transport = new TransportRegistry();
|
||||
$transport->setTransport('testing', new HeaderTransport(static::AUTH_TOKEN_HEADER_KEY));
|
||||
$bindings[TransportRegistry::class] = $transport;
|
||||
$bindings[TransportRegistry::class] = $this->createFakeTransportRegistry();
|
||||
}
|
||||
|
||||
if ($this->session) {
|
||||
@@ -461,4 +459,29 @@ class FakeHttp
|
||||
throw new \InvalidArgumentException('$data should be an array or an object.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The fake token travels in the {@see self::AUTH_TOKEN_HEADER_KEY} header, so every transport the
|
||||
* application has configured is replaced by one that reads it. The configured names are kept because
|
||||
* {@see \Spiral\Auth\Middleware\AuthTransportMiddleware} resolves a transport by name.
|
||||
*/
|
||||
private function createFakeTransportRegistry(): TransportRegistry
|
||||
{
|
||||
$registry = new TransportRegistry();
|
||||
$transport = new HeaderTransport(static::AUTH_TOKEN_HEADER_KEY);
|
||||
|
||||
try {
|
||||
$configured = $this->container->get(TransportRegistry::class);
|
||||
foreach (\array_keys($configured->getTransports()) as $name) {
|
||||
$registry->setTransport((string) $name, $transport);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Nothing to copy the names from; the fake transport is all there is.
|
||||
}
|
||||
|
||||
$registry->setTransport('testing', $transport);
|
||||
$registry->setDefaultTransport('testing');
|
||||
|
||||
return $registry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\Http;
|
||||
|
||||
use Spiral\Auth\Middleware\AuthMiddleware;
|
||||
use Spiral\Bootloader\Auth\HttpAuthBootloader;
|
||||
use Spiral\Testing\Tests\Http\Stub\ActorReportingMiddleware;
|
||||
use Spiral\Testing\Tests\Http\Stub\HeaderAuthMiddleware;
|
||||
use Spiral\Testing\Tests\TestCase;
|
||||
|
||||
final class FakeHttpAuthTransportTest extends TestCase
|
||||
{
|
||||
public function defineBootloaders(): array
|
||||
{
|
||||
return [...parent::defineBootloaders(), HttpAuthBootloader::class];
|
||||
}
|
||||
|
||||
public function testActorIsAuthenticatedThroughNamedTransport(): void
|
||||
{
|
||||
$actor = new \stdClass();
|
||||
|
||||
$response = $this->fakeHttp()
|
||||
->withActor($actor)
|
||||
->withMiddleware(ActorReportingMiddleware::class, HeaderAuthMiddleware::class)
|
||||
->get('/get/query-params');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertBodySame($actor::class);
|
||||
}
|
||||
|
||||
public function testActorIsAuthenticatedThroughEveryTransport(): void
|
||||
{
|
||||
$actor = new \stdClass();
|
||||
|
||||
$response = $this->fakeHttp()
|
||||
->withActor($actor)
|
||||
->withMiddleware(ActorReportingMiddleware::class, AuthMiddleware::class)
|
||||
->get('/get/query-params');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertBodySame($actor::class);
|
||||
}
|
||||
|
||||
public function testRequestWithoutActorStaysUnauthenticated(): void
|
||||
{
|
||||
$response = $this->fakeHttp()
|
||||
->withMiddleware(ActorReportingMiddleware::class, HeaderAuthMiddleware::class)
|
||||
->get('/get/query-params');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertBodySame(ActorReportingMiddleware::NO_ACTOR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\Http\Stub;
|
||||
|
||||
use Nyholm\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Spiral\Auth\AuthContextInterface;
|
||||
use Spiral\Auth\Middleware\AuthMiddleware;
|
||||
|
||||
final class ActorReportingMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public const NO_ACTOR = 'guest';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$context = $request->getAttribute(AuthMiddleware::ATTRIBUTE);
|
||||
$actor = $context instanceof AuthContextInterface ? $context->getActor() : null;
|
||||
|
||||
return new Response(200, body: $actor === null ? self::NO_ACTOR : $actor::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\Http\Stub;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Spiral\Auth\ActorProviderInterface;
|
||||
use Spiral\Auth\Middleware\AuthTransportMiddleware;
|
||||
use Spiral\Auth\TokenStorageInterface;
|
||||
use Spiral\Auth\TransportRegistry;
|
||||
use Spiral\Core\Attribute\Proxy;
|
||||
use Spiral\Core\ScopeInterface;
|
||||
|
||||
final class HeaderAuthMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[Proxy] private readonly ContainerInterface $container,
|
||||
) {}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$middleware = new AuthTransportMiddleware(
|
||||
'header',
|
||||
$this->container->get(ScopeInterface::class),
|
||||
$this->container->get(ActorProviderInterface::class),
|
||||
$this->container->get(TokenStorageInterface::class),
|
||||
$this->container->get(TransportRegistry::class),
|
||||
);
|
||||
|
||||
return $middleware->process($request, $handler);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user