Completed
Push — master ( 5b6d3c...e19d53 )
by Marcel
03:17
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 4
    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 4
        $this->isActiveDirectoryEnabled = $isActiveDirectoryEnabled;
53 4
        $this->encoder = $encoder;
54 4
        $this->userRepository = $userRepository;
55 4
        $this->adAuth = $adAuth;
56 4
        $this->userCreator = $userCreator;
57 4
        $this->loginRoute = $loginRoute;
58 4
        $this->checkRoute = $checkRoute;
59 4
        $this->router = $router;
60 4
        $this->csrfTokenManager = $csrfTokenManager;
61 4
        $this->upnSuffixRepository = $upnSuffixRepository;
62
63 4
        $this->logger = $logger ?? new NullLogger();
64 4
    }
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 3
    public function supports(Request $request) {
77 3
        return $request->attributes->get('_route') === $this->checkRoute
78 3
            && $request->isMethod('POST');
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84 3
    public function getCredentials(Request $request) {
85
        $credentials = [
86 3
            'username' => $request->request->get('_username'),
87 3
            'password' => $request->request->get('_password'),
88 3
            'csrf_token' => $request->request->get('_csrf_token')
89
        ];
90
91 3
        $request->getSession()->set(
92 3
            Security::LAST_USERNAME,
93 3
            $credentials['username']
94
        );
95
96 3
        return $credentials;
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102 3
    public function getUser($credentials, UserProviderInterface $userProvider) {
103 3
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
104
105 3
        if(!$this->csrfTokenManager->isTokenValid($token)) {
106
            throw new InvalidCsrfTokenException();
107
        }
108
109
        try {
110 3
            $user = $userProvider->loadUserByUsername($credentials['username']);
111
112 3
            if($user instanceof ActiveDirectoryUser) {
113 3
                $user = $this->authenticateUsingActiveDirectory(new Credentials($credentials['username'], $credentials['password']), $user);
114
            }
115
        } catch(UsernameNotFoundException $e) {
116
            $user = $this->authenticateUsingActiveDirectory(new Credentials($credentials['username'], $credentials['password']));
117
        }
118
119 3
        if($user === null) {
120
            throw new CustomUserMessageAuthenticationException('invalid_credentials');
121
        }
122
123 3
        return $user;
124
    }
125
126
    /**
127
     * @inheritDoc
128
     */
129 3
    public function checkCredentials($credentials, UserInterface $user) {
130 3
        return $this->encoder->isPasswordValid($user, $credentials['password'])
131 3
            && ((!$user instanceof User) || $user->isDeleted() === false);          // Important: check if user was deleted
132
    }
133
134
    /**
135
     * @inheritDoc
136
     */
137 3
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
138 3
        if($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
139 3
            return new RedirectResponse($targetPath);
140
        }
141
142
        return new RedirectResponse('/');
143
    }
144
145
    protected function authenticateUsingActiveDirectory(Credentials $credentials, ActiveDirectoryUser $adUser = null) {
146
        if($this->isActiveDirectoryEnabled !== true) {
147
            return $adUser;
148
        }
149
150
        $upnSuffix = substr($credentials->getUsername(), strpos($credentials->getUsername(), '@') + 1);
151
        $upnSuffixes = array_map(function(ActiveDirectoryUpnSuffix $suffix) { return $suffix->getSuffix(); }, $this->upnSuffixRepository->findAll());
152
153
        if(count($upnSuffixes) > 0 && !in_array($upnSuffix, $upnSuffixes)) {
154
            $this->logger->debug(sprintf('UPN-Suffix "%s" is not enabled for AD sync.', $upnSuffix));
155
            return $adUser;
156
        }
157
158
        try {
159
            /** @var AuthenticationResponse $response */
160
            $response = $this->adAuth->authenticate($credentials);
161
162
            if($response->isSuccess() !== true) {
163
                $this->logger
164
                    ->notice(sprintf('Failed to authenticate "%s" using Active Directory', $credentials->getUsername()));
165
166
                // password not valid
167
                return null;
168
            }
169
170
            $info = $this->transformResponse($response);
171
172
            if($this->userCreator->canCreateUser($info)) {
173
                $user = $this->userCreator->createUser($info, $adUser);
174
175
                $user->setPassword($this->encoder->encodePassword($user, $credentials->getPassword()));
176
177
                $this->userRepository->persist($user);
178
179
                return $user;
180
            } else {
181
                $this->logger
182
                    ->notice(sprintf('User "%s" tried to authenticate but this user cannot be created from active directory', $credentials->getUsername()));
183
184
                throw new CustomUserMessageAuthenticationException('not_allowed');
185
            }
186
        } catch(SocketException $e) {
187
            $this->logger
188
                ->critical('Authentication server is not available');
189
190
            throw new CustomUserMessageAuthenticationException('server_unavailable');
191
        } catch(\Exception $e) {
192
            $this->logger->critical(
193
                'Authentication failed', [
194
                    'exception' => $e
195
                ]
196
            );
197
198
            throw new CustomUserMessageAuthenticationException('unknown_error');
199
        }
200
    }
201
202
    private function transformResponse(AuthenticationResponse $response): ActiveDirectoryUserInformation {
203
        return (new ActiveDirectoryUserInformation())
204
            ->setUsername($response->getUsername())
205
            ->setUserPrincipalName($response->getUserPrincipalName())
206
            ->setFirstname($response->getFirstname())
207
            ->setLastname($response->getLastname())
208
            ->setEmail($response->getEmail())
209
            ->setGuid($response->getGuid())
210
            ->setUniqueId($response->getUniqueId())
211
            ->setOu($response->getOu())
212
            ->setGroups($response->getGroups());
213
    }
214
}