Completed
Push — master ( eaf36c...f92548 )
by Marcel
03:19
created

UserAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 11
c 2
b 0
f 0
nc 1
nop 11
dl 0
loc 15
ccs 12
cts 12
cp 1
crap 1
rs 9.9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace App\Security;
4
5
use AdAuth\AdAuthInterface;
6
use AdAuth\Credentials;
7
use AdAuth\Response\AuthenticationResponse;
8
use AdAuth\SocketException;
9
use App\Entity\ActiveDirectoryUpnSuffix;
10
use App\Entity\ActiveDirectoryUser;
11
use App\Entity\User;
12
use App\Repository\ActiveDirectoryUpnSuffixRepositoryInterface;
13
use App\Repository\UserRepositoryInterface;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\Routing\RouterInterface;
19
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
21
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
22
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
23
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
24
use Symfony\Component\Security\Core\Security;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\User\UserProviderInterface;
27
use Symfony\Component\Security\Csrf\CsrfToken;
28
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
29
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
30
use Symfony\Component\Security\Http\Util\TargetPathTrait;
31
32
class UserAuthenticator extends AbstractFormLoginAuthenticator {
33
34
    use TargetPathTrait;
35
36
    private $isActiveDirectoryEnabled;
37
    private $encoder;
38
    private $logger;
39
    private $adAuth;
40
    private $userCreator;
41
    private $userRepository;
42
    private $upnSuffixRepository;
43
44
    private $loginRoute;
45
    private $checkRoute;
46
    private $router;
47
    private $csrfTokenManager;
48
49 6
    public function __construct($isActiveDirectoryEnabled, $loginRoute, $checkRoute, UserPasswordEncoderInterface $encoder, UserRepositoryInterface $userRepository,
50
                                AdAuthInterface $adAuth, UserCreator $userCreator, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager,
51
                                ActiveDirectoryUpnSuffixRepositoryInterface $upnSuffixRepository, LoggerInterface $logger = null) {
52 6
        $this->isActiveDirectoryEnabled = $isActiveDirectoryEnabled;
53 6
        $this->encoder = $encoder;
54 6
        $this->userRepository = $userRepository;
55 6
        $this->adAuth = $adAuth;
56 6
        $this->userCreator = $userCreator;
57 6
        $this->loginRoute = $loginRoute;
58 6
        $this->checkRoute = $checkRoute;
59 6
        $this->router = $router;
60 6
        $this->csrfTokenManager = $csrfTokenManager;
61 6
        $this->upnSuffixRepository = $upnSuffixRepository;
62
63 6
        $this->logger = $logger ?? new NullLogger();
64 6
    }
65
66
    /**
67
     * @inheritDoc
68
     */
69 3
    protected function getLoginUrl() {
70 3
        return $this->router->generate($this->loginRoute);
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76 5
    public function supports(Request $request) {
77 5
        return $request->attributes->get('_route') === $this->checkRoute
78 5
            && $request->isMethod('POST');
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84 5
    public function getCredentials(Request $request) {
85
        $credentials = [
86 5
            'username' => $request->request->get('_username'),
87 5
            'password' => $request->request->get('_password'),
88 5
            'csrf_token' => $request->request->get('_csrf_token')
89
        ];
90
91 5
        $request->getSession()->set(
92 5
            Security::LAST_USERNAME,
93 5
            $credentials['username']
94
        );
95
96 5
        return $credentials;
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102 5
    public function getUser($credentials, UserProviderInterface $userProvider) {
103 5
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
104
105 5
        if(!$this->csrfTokenManager->isTokenValid($token)) {
106
            throw new InvalidCsrfTokenException();
107
        }
108
109
        try {
110
            /** @var User $user */
111 5
            $user = $userProvider->loadUserByUsername($credentials['username']);
112
113 5
            if($user->isProvisioned() === false) {
114
                throw new CustomUserMessageAuthenticationException('not_provisioned');
115
            }
116
117 5
            if($user instanceof ActiveDirectoryUser) {
118 5
                $user = $this->authenticateUsingActiveDirectory(new Credentials($credentials['username'], $credentials['password']), $user);
119
            }
120
        } catch(UsernameNotFoundException $e) {
121
            $user = $this->authenticateUsingActiveDirectory(new Credentials($credentials['username'], $credentials['password']));
122
        }
123
124 5
        if($user === null) {
125
            throw new CustomUserMessageAuthenticationException('invalid_credentials');
126
        }
127
128 5
        return $user;
129
    }
130
131
    /**
132
     * @inheritDoc
133
     */
134 5
    public function checkCredentials($credentials, UserInterface $user) {
135 5
        return $this->encoder->isPasswordValid($user, $credentials['password'])
136 5
            && ((!$user instanceof User) || $user->isDeleted() === false);          // Important: check if user was deleted
137
    }
138
139
    /**
140
     * @inheritDoc
141
     */
142 5
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
143 5
        if($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
144 3
            return new RedirectResponse($targetPath);
145
        }
146
147 2
        return new RedirectResponse('/');
148
    }
149
150
    protected function authenticateUsingActiveDirectory(Credentials $credentials, ActiveDirectoryUser $adUser = null) {
151
        if($this->isActiveDirectoryEnabled !== true) {
152
            return $adUser;
153
        }
154
155
        $upnSuffix = substr($credentials->getUsername(), strpos($credentials->getUsername(), '@') + 1);
156
        $upnSuffixes = array_map(function(ActiveDirectoryUpnSuffix $suffix) { return $suffix->getSuffix(); }, $this->upnSuffixRepository->findAll());
157
158
        if(count($upnSuffixes) > 0 && !in_array($upnSuffix, $upnSuffixes)) {
159
            $this->logger->debug(sprintf('UPN-Suffix "%s" is not enabled for AD sync.', $upnSuffix));
160
            return $adUser;
161
        }
162
163
        try {
164
            /** @var AuthenticationResponse $response */
165
            $response = $this->adAuth->authenticate($credentials);
166
167
            if($response->isSuccess() !== true) {
168
                $this->logger
169
                    ->notice(sprintf('Failed to authenticate "%s" using Active Directory', $credentials->getUsername()));
170
171
                // password not valid
172
                return null;
173
            }
174
175
            $info = $this->transformResponse($response);
176
177
            if($this->userCreator->canCreateUser($info)) {
178
                $user = $this->userCreator->createUser($info, $adUser);
179
180
                $user->setPassword($this->encoder->encodePassword($user, $credentials->getPassword()));
181
182
                $this->userRepository->persist($user);
183
184
                return $user;
185
            } else {
186
                $this->logger
187
                    ->notice(sprintf('User "%s" tried to authenticate but this user cannot be created from active directory', $credentials->getUsername()));
188
189
                throw new CustomUserMessageAuthenticationException('not_allowed');
190
            }
191
        } catch(SocketException $e) {
192
            $this->logger
193
                ->critical('Authentication server is not available');
194
195
            throw new CustomUserMessageAuthenticationException('server_unavailable');
196
        } catch(\Exception $e) {
197
            $this->logger->critical(
198
                'Authentication failed', [
199
                    'exception' => $e
200
                ]
201
            );
202
203
            throw new CustomUserMessageAuthenticationException('unknown_error');
204
        }
205
    }
206
207
    private function transformResponse(AuthenticationResponse $response): ActiveDirectoryUserInformation {
208
        return (new ActiveDirectoryUserInformation())
209
            ->setUsername($response->getUsername())
210
            ->setUserPrincipalName($response->getUserPrincipalName())
211
            ->setFirstname($response->getFirstname())
212
            ->setLastname($response->getLastname())
213
            ->setEmail($response->getEmail())
214
            ->setGuid($response->getGuid())
215
            ->setUniqueId($response->getUniqueId())
216
            ->setOu($response->getOu())
217
            ->setGroups($response->getGroups());
218
    }
219
}