Completed
Push — master ( f03e1f...35703e )
by Chad
01:51
created

LdapGuardAuthenticator::configureAuthHandlers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
/**
3
 * This file is part of the LdapToolsBundle package.
4
 *
5
 * (c) Chad Sikorra <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LdapTools\Bundle\LdapToolsBundle\Security;
12
13
use LdapTools\Bundle\LdapToolsBundle\Event\AuthenticationHandlerEvent;
14
use LdapTools\Bundle\LdapToolsBundle\Event\LdapLoginEvent;
15
use LdapTools\Exception\Exception;
16
use LdapTools\Operation\AuthenticationOperation;
17
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
21
use Symfony\Component\Security\Core\Exception\AuthenticationException;
22
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
23
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
24
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
25
use Symfony\Component\Security\Core\Security;
26
use Symfony\Component\Security\Core\User\UserInterface;
27
use Symfony\Component\Security\Core\User\UserProviderInterface;
28
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
29
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
30
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
31
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
32
use LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUserChecker;
33
use LdapTools\Exception\LdapConnectionException;
34
use LdapTools\LdapManager;
35
36
/**
37
 * LDAP Guard Authenticator.
38
 *
39
 * @author Chad Sikorra <[email protected]>
40
 */
41
class LdapGuardAuthenticator extends AbstractGuardAuthenticator
42
{
43
    /**
44
     * @var LdapManager
45
     */
46
    protected $ldap;
47
48
    /**
49
     * @var LdapUserChecker
50
     */
51
    protected $userChecker;
52
53
    /**
54
     * @var string
55
     */
56
    protected $domain;
57
58
    /**
59
     * @var EventDispatcherInterface
60
     */
61
    protected $dispatcher;
62
63
    /**
64
     * @var string The entry point/start path route name.
65
     */
66
    protected $startPath = 'login';
67
68
    /**
69
     * @var AuthenticationSuccessHandlerInterface
70
     */
71
    protected $successHandler;
72
73
    /**
74
     * @var AuthenticationFailureHandlerInterface
75
     */
76
    protected $failureHandler;
77
78
    /**
79
     * @var AuthenticationEntryPointInterface
80
     */
81
    protected $entryPoint;
82
83
    /**
84
     * @var array
85
     */
86
    protected $options = [
87
        'hide_user_not_found_exceptions' => true,
88
        'username_parameter' => '_username',
89
        'password_parameter' => '_password',
90
        'domain_parameter' => '_ldap_domain',
91
    ];
92
93
    /**
94
     * @param bool $hideUserNotFoundExceptions
95
     * @param LdapUserChecker $userChecker
96
     * @param LdapManager $ldap
97
     * @param AuthenticationEntryPointInterface $entryPoint
98
     * @param EventDispatcherInterface $dispatcher
99
     * @param AuthenticationSuccessHandlerInterface $successHandler
100
     * @param AuthenticationFailureHandlerInterface $failureHandler
101
     * @param array $options
102
     */
103
    public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker $userChecker, LdapManager $ldap, AuthenticationEntryPointInterface $entryPoint, EventDispatcherInterface $dispatcher, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options)
104
    {
105
        $this->userChecker = $userChecker;
106
        $this->ldap = $ldap;
107
        $this->entryPoint = $entryPoint;
108
        $this->dispatcher = $dispatcher;
109
        $this->successHandler = $successHandler;
110
        $this->failureHandler = $failureHandler;
111
        $this->options['hide_user_not_found_exceptions'] = $hideUserNotFoundExceptions;
112
        $this->options = array_merge($this->options, $options);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function getCredentials(Request $request)
119
    {
120
        $credentials = [
121
            'username' => $this->getRequestParameter($this->options['username_parameter'], $request),
122
            'password' => $this->getRequestParameter($this->options['password_parameter'], $request),
123
            'ldap_domain' => $this->getRequestParameter($this->options['domain_parameter'], $request),
124
        ];
125
        if (empty($credentials['username'])) {
126
            return null;
127
        }
128
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']);
129
130
        return $credentials;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getUser($credentials, UserProviderInterface $userProvider)
137
    {
138
        $domain = $this->ldap->getDomainContext();
139
140
        try {
141
            $this->switchDomainIfNeeded($credentials);
142
            $user = $userProvider->loadUserByUsername($credentials['username']);
143
            $this->userChecker->checkPreAuth($user);
144
145
            return $user;
146
        } catch (UsernameNotFoundException $e) {
147
            $this->hideOrThrow($e);
148
        } catch (BadCredentialsException $e) {
149
            $this->hideOrThrow($e);
150
        } catch (LdapConnectionException $e) {
151
            $this->hideOrThrow($e);
152
        } catch (\Exception $e) {
153
            $this->hideOrThrow($e);
154
        } finally {
155
            $this->switchDomainBackIfNeeded($domain);
156
        }
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function checkCredentials($credentials, UserInterface $user)
163
    {
164
        $domain = $this->ldap->getDomainContext();
165
166
        try {
167
            $this->switchDomainIfNeeded($credentials);
168
            /** @var \LdapTools\Operation\AuthenticationResponse $response */
169
            $response = $this->ldap->getConnection()->execute(
170
                new AuthenticationOperation($user->getUsername(), $credentials['password'])
171
            );
172 View Code Duplication
            if (!$response->isAuthenticated()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
                $this->userChecker->checkLdapErrorCode(
174
                    $user,
175
                    $response->getErrorCode(),
176
                    $this->ldap->getConnection()->getConfig()->getLdapType()
177
                );
178
                throw new CustomUserMessageAuthenticationException(
179
                    $response->getErrorMessage(), [], $response->getErrorCode()
180
                );
181
            }
182
            // No way to get the token from the Guard, need to create one to pass...
183
            $token = new UsernamePasswordToken($user, $credentials['password'], 'ldap-tools', $user->getRoles());
184
            $token->setAttribute('ldap_domain', isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '');
185
            $this->dispatcher->dispatch(
186
                LdapLoginEvent::SUCCESS,
187
                new LdapLoginEvent($user, $token)
188
            );
189
        } catch (\Exception $e) {
190
            $this->hideOrThrow($e);
191
        } finally {
192
            $this->domain = $this->ldap->getDomainContext();
193
            $this->switchDomainBackIfNeeded($domain);
194
        }
195
196
        return true;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 View Code Duplication
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $event = new AuthenticationHandlerEvent(
205
            $this->successHandler->onAuthenticationSuccess($request, $token),
206
            $request,
207
            null,
208
            $token,
209
            $providerKey
210
        );
211
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event);
212
213
        return $event->getResponse();
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 View Code Duplication
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
    {
221
        $event = new AuthenticationHandlerEvent(
222
            $this->failureHandler->onAuthenticationFailure($request, $exception),
223
            $request,
224
            $exception
225
        );
226
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event);
227
228
        return $event->getResponse();
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234 View Code Duplication
    public function start(Request $request, AuthenticationException $authException = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
235
    {
236
        $event = new AuthenticationHandlerEvent(
237
            $this->entryPoint->start($request, $authException),
238
            $request,
239
            $authException
240
        );
241
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::START, $event);
242
243
        return $event->getResponse();
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function supportsRememberMe()
250
    {
251
        return false;
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function createAuthenticatedToken(UserInterface $user, $providerKey)
258
    {
259
        $token = parent::createAuthenticatedToken($user, $providerKey);
260
        $token->setAttribute('ldap_domain', $this->domain);
261
262
        return $token;
263
    }
264
265
    /**
266
     * @param string $param
267
     * @param Request $request
268
     * @return string|null
269
     */
270
    protected function getRequestParameter($param, Request $request)
271
    {
272
        return $request->request->get($param) ?: $request->get($param);
273
    }
274
275
    /**
276
     * If the domain needs to a different context for the request, then switch it.
277
     *
278
     * @param array $credentials
279
     */
280
    protected function switchDomainIfNeeded($credentials)
281
    {
282
        if (!empty($credentials['ldap_domain']) && $this->ldap->getDomainContext() !== $credentials['ldap_domain']) {
283
            $this->ldap->switchDomain($credentials['ldap_domain']);
284
        }
285
    }
286
287
    /**
288
     * If the passed domain is not the current context, then switch back to it.
289
     *
290
     * @param string $domain
291
     */
292
    protected function switchDomainBackIfNeeded($domain)
293
    {
294
        if ($domain !== $this->ldap->getDomainContext()) {
295
            $this->ldap->switchDomain($domain);
296
        }
297
    }
298
299
    /**
300
     * Determine whether or not the exception should be masked with a BadCredentials or not.
301
     *
302
     * @param \Exception $e
303
     * @throws \Exception
304
     */
305
    protected function hideOrThrow(\Exception $e)
306
    {
307
        if ($this->options['hide_user_not_found_exceptions']) {
308
            throw new BadCredentialsException('Bad credentials.', 0, $e);
309
        }
310
        // Specifically show LdapTools related exceptions, ignore others.
311
        if (!$this->options['hide_user_not_found_exceptions'] && $e instanceof Exception) {
312
            throw new CustomUserMessageAuthenticationException($e->getMessage(), [], $e->getCode());
313
        }
314
315
        throw $e;
316
    }
317
}
318