Passed
Branch master (372b2a)
by Filipe
01:32
created

SecurityProfileFactory::createStatelessProfile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 12
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
/**
4
 * This file is part of web-stack
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\Domain\Security\Http;
13
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Container\NotFoundExceptionInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use Slick\Di\ContainerInterface;
18
use Slick\WebStack\Domain\Security\Authentication\Token\TokenValidator\UserIntegrityTokenValidator;
19
use Slick\WebStack\Domain\Security\Http\SecurityProfile\DisabledSecurityProfile;
20
use Slick\WebStack\Domain\Security\Http\SecurityProfile\SecurityProfile;
21
use Slick\WebStack\Domain\Security\Http\SecurityProfile\SecurityProfileTrait;
22
use Slick\WebStack\Domain\Security\Http\SecurityProfile\StatefulSecurityProfile\SessionSecurityProfile;
23
use Slick\WebStack\Domain\Security\User\UserProviderInterface;
24
use Slick\WebStack\Domain\Security\UserInterface;
25
use Slick\WebStack\Infrastructure\Http\Authenticator\Factory\AuthenticatorsListFactory;
26
27
/**
28
 * Class SecurityProfileFactory
29
 */
30
class SecurityProfileFactory
31
{
32
    use SecurityProfileTrait;
33
34
    /**
35
     * @use ProfileFactoryTrait<UserInterface>
36
     */
37
    use ProfileFactoryTrait;
38
39
    protected ?SecurityProfileInterface $profile = null;
40
41
    private ?AuthenticationEntryPointInterface $entryPoint = null;
42
43
    /**
44
     * Creates a SecurityProfileFactory
45
     *
46
     * @param ContainerInterface $container
47
     */
48
    public function __construct(private readonly ContainerInterface $container)
49
    {
50
    }
51
52
    /**
53
     * Create a security profile based on a given profile configuration and request.
54
     *
55
     * @param array<string, mixed> $profileConfig The profile configuration.
56
     * @param ServerRequestInterface $request The server request.
57
     *
58
     * @return SecurityProfileInterface|null The created security profile, or null if no match found.
59
     * @throws ContainerExceptionInterface
60
     * @throws NotFoundExceptionInterface
61
     */
62
    public function createProfile(array $profileConfig, ServerRequestInterface $request): ?SecurityProfileInterface
63
    {
64
        foreach ($profileConfig['profiles'] as $profile) {
65
            if (!$this->matchExp = $profile['pattern'] ?? null) {
66
                continue;
67
            }
68
69
            if ($this->match($request)) {
70
                $this->profile = $this->createDisabledProfile($profile);
71
                return $this->profile;
72
            }
73
        }
74
75
        return null;
76
    }
77
78
    /**
79
     * Retrieves the security profile
80
     *
81
     * @return SecurityProfileInterface|null The security profile if available, null otherwise
82
     */
83
    public function profile(): ?SecurityProfileInterface
84
    {
85
        return $this->profile;
86
    }
87
88
    /**
89
     * Create a disabled security profile based on a given profile.
90
     *
91
     * @param array<string, mixed> $profile The profile configuration.
92
     * @return SecurityProfileInterface The created disabled security profile.
93
     *
94
     * @throws ContainerExceptionInterface
95
     * @throws NotFoundExceptionInterface
96
     */
97
    private function createDisabledProfile(array $profile): SecurityProfileInterface
98
    {
99
        $options = array_merge(self::$defaultOptions, $profile);
100
101
        if ($options['secured'] !== true) {
102
            if (isset($options['stateless']) && !$options['stateless']) {
103
                $tokenValidator = new UserIntegrityTokenValidator(
104
                    $this->createUserProvider(
105
                        $this->getProviderClass($profile)
106
                    )
107
                );
108
109
                return new DisabledSecurityProfile(
110
                    $options['pattern'],
111
                    $this->createTokenStorage($profile),
112
                    $this->createSessionDriver($profile),
113
                    $tokenValidator
114
                );
115
            }
116
            return new DisabledSecurityProfile($options['pattern']);
117
        }
118
119
        return $this->createStatelessProfile($options);
120
    }
121
122
    /**
123
     * Create a stateless security profile based on a given profile modules.
124
     *
125
     * @param array<string, mixed> $profile The profile configuration.
126
     * @return SecurityProfileInterface A stateless security profile, or null if the profile is not stateless.
127
     *
128
     * @throws ContainerExceptionInterface
129
     * @throws NotFoundExceptionInterface
130
     */
131
    private function createStatelessProfile(array $profile): SecurityProfileInterface
132
    {
133
        if ($profile['stateless'] === true) {
134
            return new SecurityProfile(
135
                $profile['pattern'],
136
                $this->createAuthenticatorManager($profile),
137
                $this->createTokenStorage($profile),
138
                $this->createEntryPoint($profile)
139
            );
140
        }
141
142
        return $this->createSecurityProfile($profile);
143
    }
144
145
    /**
146
     * Create a security profile based on a given profile configuration.
147
     *
148
     * @param array<string, mixed> $profile The profile configuration.
149
     * @return SecurityProfileInterface The created security profile.
150
     *
151
     * @throws ContainerExceptionInterface
152
     * @throws NotFoundExceptionInterface
153
     */
154
    private function createSecurityProfile(array $profile): SecurityProfileInterface
155
    {
156
        $tokenValidator = new UserIntegrityTokenValidator(
157
            $this->createUserProvider($this->getProviderClass($profile))
158
        );
159
        return new SessionSecurityProfile(
160
            $profile['pattern'],
161
            $this->createAuthenticatorManager($profile),
162
            $this->createTokenStorage($profile),
163
            $this->createSessionDriver($profile),
164
            $this->createEntryPoint($profile),
165
            $tokenValidator
166
        );
167
    }
168
169
    /**
170
     * Creates an authenticator manager instance based on a given profile.
171
     *
172
     * @param array<string, mixed> $profile The profile configuration
173
     *
174
     * @return AuthenticatorManagerInterface The authenticator manager instance
175
     *
176
     * @throws ContainerExceptionInterface
177
     * @throws NotFoundExceptionInterface
178
     */
179
    private function createAuthenticatorManager(array $profile): AuthenticatorManagerInterface
180
    {
181
        $authenticators = new AuthenticatorsListFactory($this->container, $profile['authenticators']);
182
        $this->entryPoint = $authenticators->entryPoint();
183
        $args = [
184
            'authenticators' => $authenticators,
185
            'tokenStorage' => $this->createTokenStorage($profile),
186
            'hasher' => $this->createPasswordHasher($profile)
187
        ];
188
        return $this->container->make(AuthenticatorManager::class, ...array_values($args));
189
    }
190
191
    /**
192
     * Create an authentication entry point based on a given profile.
193
     *
194
     * @param array<string, mixed> $profile The profile configuration.
195
     * @return AuthenticationEntryPointInterface|null The created authentication entry point or null if
196
     *                                                no entry point is specified in the profile.
197
     * @throws ContainerExceptionInterface
198
     * @throws NotFoundExceptionInterface
199
     */
200
    private function createEntryPoint(array $profile): ?AuthenticationEntryPointInterface
201
    {
202
        $config = array_merge(self::$defaultOptions, $profile);
203
        if ($config['entryPoint'] !== null) {
204
            return $this->container->get($config['entryPoint']);
205
        }
206
207
        return $this->entryPoint;
208
    }
209
210
    /**
211
     * @param array<string, mixed> $profile
212
     * @return string
213
     */
214
    private function getProviderClass(array $profile): string
215
    {
216
        return isset($profile['userProvider']) ? $profile['userProvider'] : UserProviderInterface::class;
217
    }
218
}
219