Passed
Pull Request — master (#1104)
by Maxim
10:52
created

HttpAuthBootloader::getDefaultTokenStorage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Bootloader\Auth;
6
7
use Psr\Http\Message\ServerRequestInterface;
8
use Spiral\Auth\AuthContextInterface;
9
use Spiral\Auth\Config\AuthConfig;
10
use Spiral\Auth\HttpTransportInterface;
11
use Spiral\Auth\Middleware\AuthMiddleware;
12
use Spiral\Auth\Session\TokenStorage as SessionTokenStorage;
13
use Spiral\Auth\TokenStorageInterface;
14
use Spiral\Auth\TokenStorageProvider;
15
use Spiral\Auth\TokenStorageProviderInterface;
16
use Spiral\Auth\Transport\CookieTransport;
17
use Spiral\Auth\Transport\HeaderTransport;
18
use Spiral\Auth\TransportRegistry;
19
use Spiral\Boot\AbstractKernel;
20
use Spiral\Boot\Bootloader\Bootloader;
21
use Spiral\Boot\EnvironmentInterface;
22
use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException;
23
use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException;
24
use Spiral\Bootloader\Http\HttpBootloader;
25
use Spiral\Config\ConfiguratorInterface;
26
use Spiral\Config\Patch\Append;
27
use Spiral\Core\Attribute\Singleton;
28
use Spiral\Core\BinderInterface;
29
use Spiral\Core\Config\Proxy;
30
use Spiral\Core\Container\Autowire;
31
use Spiral\Core\FactoryInterface;
32
use Spiral\Framework\Spiral;
33
use Spiral\Http\Config\HttpConfig;
34
use Spiral\Http\CurrentRequest;
35
36
/**
37
 * Enables Auth middleware and http transports to read and write tokens in PSR-7 request/response.
38
 */
39
#[Singleton]
40
final class HttpAuthBootloader extends Bootloader
41
{
42 355
    public function __construct(
43
        private readonly ConfiguratorInterface $config,
44
        private readonly BinderInterface $binder,
45
    ) {
46 355
    }
47
48 355
    public function defineDependencies(): array
49
    {
50 355
        return [
51 355
            AuthBootloader::class,
52 355
            HttpBootloader::class,
53 355
        ];
54
    }
55
56 355
    public function defineBindings(): array
57
    {
58 355
        $this->binder
59 355
            ->getBinder(Spiral::Http)
0 ignored issues
show
Bug introduced by
The method getBinder() does not exist on Spiral\Core\BinderInterface. It seems like you code against a sub-type of Spiral\Core\BinderInterface such as Spiral\Core\Container. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
            ->/** @scrutinizer ignore-call */ 
60
              getBinder(Spiral::Http)
Loading history...
60 355
            ->bind(
61 355
                AuthContextInterface::class,
62 355
                static fn (?ServerRequestInterface $request): AuthContextInterface =>
63 8
                    ($request ?? throw new InvalidRequestScopeException(AuthContextInterface::class))
64 8
                        ->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new ContextualObjectNotFoundException(
65 8
                            AuthContextInterface::class,
66 8
                            AuthMiddleware::ATTRIBUTE,
67 8
                        )
68 355
            );
69 355
        $this->binder->bind(AuthContextInterface::class, new Proxy(AuthContextInterface::class, false));
70
71 355
        return [];
72
    }
73
74 355
    public function defineSingletons(): array
75
    {
76
        // Default token storage outside of HTTP scope
77 355
        $this->binder->bindSingleton(
78 355
            TokenStorageInterface::class,
79 355
            static fn (TokenStorageProviderInterface $provider): TokenStorageInterface => $provider->getStorage(),
80 355
        );
81
82
        // Token storage from request attribute in HTTP scope
83 355
        $this->binder
84 355
            ->getBinder(Spiral::Http)
85 355
            ->bindSingleton(TokenStorageInterface::class, [self::class, 'getTokenStorage']);
86
87 355
        return [
88 355
            TransportRegistry::class => [self::class, 'transportRegistry'],
89 355
            TokenStorageProviderInterface::class => TokenStorageProvider::class,
90 355
        ];
91
    }
92
93 355
    public function init(AbstractKernel $kernel, EnvironmentInterface $env): void
94
    {
95 355
        $this->config->setDefaults(
96 355
            AuthConfig::CONFIG,
97 355
            [
98 355
                'defaultTransport' => $env->get('AUTH_TOKEN_TRANSPORT', 'cookie'),
99 355
                'defaultStorage' => $env->get('AUTH_TOKEN_STORAGE', 'session'),
100 355
                'transports' => [],
101 355
                'storages' => [],
102 355
            ]
103 355
        );
104
105 355
        $kernel->booting(function () {
106 355
            $this->addTransport('cookie', $this->createDefaultCookieTransport());
107 355
            $this->addTransport('header', new HeaderTransport('X-Auth-Token'));
108 355
            $this->addTokenStorage('session', SessionTokenStorage::class);
109 355
        });
110
    }
111
112
    /**
113
     * Add new Http token transport.
114
     *
115
     * @param non-empty-string $name
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
116
     * @param Autowire|HttpTransportInterface|class-string<HttpTransportInterface> $transport
117
     */
118 355
    public function addTransport(string $name, Autowire|HttpTransportInterface|string $transport): void
119
    {
120 355
        $this->config->modify(AuthConfig::CONFIG, new Append('transports', $name, $transport));
121
    }
122
123
    /**
124
     * Add new Http token storage.
125
     *
126
     * @param non-empty-string $name
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
127
     * @param Autowire|TokenStorageInterface|class-string<TokenStorageInterface> $storage
128
     */
129 355
    public function addTokenStorage(string $name, Autowire|TokenStorageInterface|string $storage): void
130
    {
131 355
        $this->config->modify(AuthConfig::CONFIG, new Append('storages', $name, $storage));
132
    }
133
134
    /**
135
     * Creates default cookie transport when "transports" section is empty.
136
     */
137 355
    private function createDefaultCookieTransport(): CookieTransport
138
    {
139 355
        $config = $this->config->getConfig(HttpConfig::CONFIG);
140
141 355
        return new CookieTransport('token', $config['basePath'] ?? '/');
142
    }
143
144
    /**
145
     * @noRector RemoveUnusedPrivateMethodRector
146
     */
147 355
    private function transportRegistry(AuthConfig $config, FactoryInterface $factory): TransportRegistry
148
    {
149 355
        $registry = new TransportRegistry();
150 355
        $registry->setDefaultTransport($config->getDefaultTransport());
151
152 355
        foreach ($config->getTransports() as $name => $transport) {
153 355
            if ($transport instanceof Autowire) {
154 355
                $transport = $transport->resolve($factory);
155
            }
156
157 355
            $registry->setTransport($name, $transport);
158
        }
159
160 355
        return $registry;
161
    }
162
163
    /**
164
     * Get default token storage from provider
165
     */
166 5
    private function getTokenStorage(
167
        TokenStorageProviderInterface $provider,
168
        ServerRequestInterface $request
169
    ): TokenStorageInterface {
170 5
        return $request->getAttribute(AuthMiddleware::TOKEN_STORAGE_ATTRIBUTE) ?? $provider->getStorage();
171
    }
172
}
173