Passed
Push — master ( 3c8625...c93166 )
by Divine Niiquaye
12:46
created

RemoteUserAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 5
dl 0
loc 12
ccs 0
cts 6
cp 0
crap 2
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 *
17
 */
18
19
namespace Biurad\Security\Authenticator;
20
21
use Biurad\Security\Interfaces\AuthenticatorInterface;
22
use Psr\Http\Message\ResponseInterface;
23
use Psr\Http\Message\ServerRequestInterface;
24
use Psr\Log\LoggerInterface;
25
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
26
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
27
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
28
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
29
use Symfony\Component\Security\Core\Exception\AuthenticationException;
30
use Symfony\Component\Security\Core\User\UserProviderInterface;
31
32
/**
33
 * This authenticator authenticates a remote user. (example by the
34
 * webserver) X.509 certificates.
35
 *
36
 * @author Divine Niiquaye Ibok <[email protected]>
37
 */
38
class RemoteUserAuthenticator implements AuthenticatorInterface
39
{
40
    private string $userKey;
41
    private string $credentialsKey;
42
    private UserProviderInterface $userProvider;
43
    private TokenStorageInterface $tokenStorage;
44
    private ?LoggerInterface $logger;
45
46
    public function __construct(
47
        UserProviderInterface $userProvider,
48
        TokenStorageInterface $tokenStorage,
49
        string $userKey = 'SSL_CLIENT_S_DN_Email',
50
        string $credentialsKey = 'SSL_CLIENT_S_DN',
51
        LoggerInterface $logger = null
52
    ) {
53
        $this->userKey = $userKey;
54
        $this->credentialsKey = $credentialsKey;
55
        $this->userProvider = $userProvider;
56
        $this->tokenStorage = $tokenStorage;
57
        $this->logger = $logger;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function setToken(?TokenInterface $token): void
64
    {
65
        if (null !== $token) {
66
            $this->tokenStorage->setToken($token);
67
        }
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function supports(ServerRequestInterface $request): bool
74
    {
75
        $token = $this->tokenStorage->getToken();
76
77
        if (null !== $token && !$token instanceof PreAuthenticatedToken) {
78
            return $request->hasHeader('AUTH-SWITCH-USER');
79
        }
80
81
        if (!$username = ($request->getServerParams()[$this->userKey] ?? null)) {
82
            $username = $request->getServerParams()[$this->credentialsKey] ?? null;
83
84
            if (null !== $username && \preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $username, $matches)) {
85
                $username = $matches[1];
86
            }
87
        }
88
89
        if (null === $username) {
90
            $this->clearToken($token);
91
92
            if (null !== $this->logger) {
93
                $this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => static::class]);
94
            }
95
96
            return false;
97
        }
98
99
        // do not overwrite already stored tokens from the same user (i.e. from the session)
100
        if ($token instanceof PreAuthenticatedToken && $token->getUserIdentifier() === $username) {
101
            if (null !== $this->logger) {
102
                $this->logger->debug('Skipping pre-authenticated authenticator as the user already has an existing session.', ['authenticator' => static::class]);
103
            }
104
105
            return false;
106
        }
107
108
        $this->userKey = $username;
109
110
        return true;
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function authenticate(ServerRequestInterface $request, array $credentials): ?TokenInterface
117
    {
118
        if (!empty($credentials)) {
119
            return null;
120
        }
121
        $user = $this->userProvider->loadUserByIdentifier($this->userKey);
122
123
        if ($request->hasHeader('AUTH-SWITCH-USER')) {
124
            $token = $this->tokenStorage->getToken();
125
126
            if ($token && $this->userKey !== $token->getUserIdentifier()) {
127
                $token = new SwitchUserToken($user, 'main', $user->getRoles(), $token, (string) $request->getUri());
128
            }
129
        }
130
131
        return $token ?? new PreAuthenticatedToken($user, 'main', $user->getRoles());
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function failure(ServerRequestInterface $request, AuthenticationException $exception): ?ResponseInterface
138
    {
139
        $this->clearToken($this->tokenStorage->getToken(), $exception);
140
141
        return null;
142
    }
143
144
    private function clearToken(?TokenInterface $token, AuthenticationException $exception = null): void
145
    {
146
        if ($token instanceof PreAuthenticatedToken) {
147
            $this->tokenStorage->setToken();
148
149
            if (null !== $this->logger) {
150
                if (null === $exception) {
151
                    $this->logger->info(\sprintf('Cleared pre-authenticated token, due missing user in credentials: %s, %s', $this->userKey, $this->credentialsKey));
152
                } else {
153
                    $this->logger->info('Cleared pre-authenticated token due to an exception.', ['exception' => $exception]);
154
                }
155
            }
156
        }
157
    }
158
}
159