Passed
Pull Request — master (#1104)
by Aleksei
26:20
created

HttpAuthBootloader::defineBindings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 16
ccs 14
cts 14
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 357
    public function __construct(
43
        private readonly ConfiguratorInterface $config,
44
        private readonly BinderInterface $binder,
45
    ) {
46 357
    }
47
48 357
    public function defineDependencies(): array
49
    {
50 357
        return [
51 357
            AuthBootloader::class,
52 357
            HttpBootloader::class,
53 357
        ];
54
    }
55
56 357
    public function defineBindings(): array
57
    {
58 357
        $this->binder
59 357
            ->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 357
            ->bind(
61 357
                AuthContextInterface::class,
62 357
                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 357
            );
69 357
        $this->binder->bind(AuthContextInterface::class, new Proxy(AuthContextInterface::class, false));
70
71 357
        return [];
72
    }
73
74 357
    public function defineSingletons(): array
75
    {
76
        // Default token storage outside of HTTP scope
77 357
        $this->binder->bindSingleton(
78 357
            TokenStorageInterface::class,
79 357
            static fn (TokenStorageProviderInterface $provider): TokenStorageInterface => $provider->getStorage(),
80 357
        );
81
82
        // Token storage from request attribute in HTTP scope
83 357
        $this->binder
84 357
            ->getBinder(Spiral::Http)
85 357
            ->bindSingleton(TokenStorageInterface::class, [self::class, 'getTokenStorage']);
86
87 357
        return [
88 357
            TransportRegistry::class => [self::class, 'transportRegistry'],
89 357
            TokenStorageProviderInterface::class => TokenStorageProvider::class,
90 357
        ];
91
    }
92
93 357
    public function init(AbstractKernel $kernel, EnvironmentInterface $env): void
94
    {
95 357
        $this->config->setDefaults(
96 357
            AuthConfig::CONFIG,
97 357
            [
98 357
                'defaultTransport' => $env->get('AUTH_TOKEN_TRANSPORT', 'cookie'),
99 357
                'defaultStorage' => $env->get('AUTH_TOKEN_STORAGE', 'session'),
100 357
                'transports' => [],
101 357
                'storages' => [],
102 357
            ]
103 357
        );
104
105 357
        $kernel->booting(function () {
106 357
            $this->addTransport('cookie', $this->createDefaultCookieTransport());
107 357
            $this->addTransport('header', new HeaderTransport('X-Auth-Token'));
108 357
            $this->addTokenStorage('session', SessionTokenStorage::class);
109 357
        });
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 357
    public function addTransport(string $name, Autowire|HttpTransportInterface|string $transport): void
119
    {
120 357
        $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 357
    public function addTokenStorage(string $name, Autowire|TokenStorageInterface|string $storage): void
130
    {
131 357
        $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 357
    private function createDefaultCookieTransport(): CookieTransport
138
    {
139 357
        $config = $this->config->getConfig(HttpConfig::CONFIG);
140
141 357
        return new CookieTransport('token', $config['basePath'] ?? '/');
142
    }
143
144
    /**
145
     * @noRector RemoveUnusedPrivateMethodRector
146
     */
147 357
    private function transportRegistry(AuthConfig $config, FactoryInterface $factory): TransportRegistry
148
    {
149 357
        $registry = new TransportRegistry();
150 357
        $registry->setDefaultTransport($config->getDefaultTransport());
151
152 357
        foreach ($config->getTransports() as $name => $transport) {
153 357
            if ($transport instanceof Autowire) {
154 357
                $transport = $transport->resolve($factory);
155
            }
156
157 357
            $registry->setTransport($name, $transport);
158
        }
159
160 357
        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