Completed
Push — master ( 80c0e8...d4e9f2 )
by Chad
04:36
created

LdapGuardAuthenticator::setOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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\Bundle\LdapToolsBundle\Security\User\LdapUserChecker;
16
use LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUserProvider;
17
use LdapTools\Exception\LdapConnectionException;
18
use LdapTools\Operation\AuthenticationOperation;
19
use LdapTools\LdapManager;
20
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21
use Symfony\Component\HttpFoundation\Request;
22
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
23
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
24
use Symfony\Component\Security\Core\Exception\AuthenticationException;
25
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
26
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
27
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
28
use Symfony\Component\Security\Core\Security;
29
use Symfony\Component\Security\Core\User\UserInterface;
30
use Symfony\Component\Security\Core\User\UserProviderInterface;
31
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
32
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
33
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
34
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
35
use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint;
36
37
/**
38
 * LDAP Guard Authenticator.
39
 *
40
 * @author Chad Sikorra <[email protected]>
41
 */
42
class LdapGuardAuthenticator extends AbstractGuardAuthenticator
43
{
44
    use LdapAuthenticationTrait;
45
46
    /**
47
     * @var LdapUserChecker
48
     */
49
    protected $userChecker;
50
51
    /**
52
     * @var string
53
     */
54
    protected $domain;
55
56
    /**
57
     * @var EventDispatcherInterface
58
     */
59
    protected $dispatcher;
60
61
    /**
62
     * @var AuthenticationSuccessHandlerInterface
63
     */
64
    protected $successHandler;
65
66
    /**
67
     * @var AuthenticationFailureHandlerInterface
68
     */
69
    protected $failureHandler;
70
71
    /**
72
     * @var AuthenticationEntryPointInterface
73
     */
74
    protected $entryPoint;
75
76
    /**
77
     * @var array
78
     */
79
    protected $options = [
80
        'hide_user_not_found_exceptions' => true,
81
        'username_parameter' => '_username',
82
        'password_parameter' => '_password',
83
        'domain_parameter' => '_ldap_domain',
84
        'post_only' => false,
85
        'remember_me' => false,
86
        'login_query_attribute' => null,
87
        'http_basic' => false,
88
        'http_basic_realm' => null,
89
        'http_basic_domain' => null,
90
    ];
91
92
    /**
93
     * @param bool $hideUserNotFoundExceptions
94
     * @param LdapUserChecker $userChecker
95
     * @param LdapManager $ldap
96
     * @param AuthenticationEntryPointInterface $entryPoint
97
     * @param EventDispatcherInterface $dispatcher
98
     * @param AuthenticationSuccessHandlerInterface $successHandler
99
     * @param AuthenticationFailureHandlerInterface $failureHandler
100
     * @param array $options
101
     * @param LdapUserProvider $ldapUserProvider
102
     */
103
    public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker $userChecker, LdapManager $ldap, AuthenticationEntryPointInterface $entryPoint, EventDispatcherInterface $dispatcher, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options, LdapUserProvider $ldapUserProvider)
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->ldapUserProvider = $ldapUserProvider;
113
        $this->options = array_merge($this->options, $options);
114
    }
115
116
    /**
117
     * Allows to more easily extend the base LDAP guard service and set specific options.
118
     *
119
     * @param array $options
120
     * @return $this
121
     */
122
    public function setOptions(array $options)
123
    {
124
        $this->options = array_merge($this->options, $options);
125
126
        return $this;
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function getCredentials(Request $request)
133
    {
134
        $credentials = [
135
            'username' => $this->getRequestUsername($request),
136
            'password' => $this->getRequestPassword($request),
137
            'ldap_domain' => $this->getRequestDomain($request),
138
        ];
139
        if (empty($credentials['username'])) {
140
            return null;
141
        }
142
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']);
143
144
        return $credentials;
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function getUser($credentials, UserProviderInterface $userProvider)
151
    {
152
        $domain = $this->ldap->getDomainContext();
153
154
        try {
155
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
156
            $this->switchDomainIfNeeded($credDomain);
157
            $this->setLdapCredentialsIfNeeded($credentials['username'], $credentials['password'], $userProvider);
158
            $user = $userProvider->loadUserByUsername($credentials['username']);
159
            $this->userChecker->checkPreAuth($user);
160
161
            return $user;
162
        } catch (UsernameNotFoundException $e) {
163
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
164
        } catch (BadCredentialsException $e) {
165
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
166
        } catch (LdapConnectionException $e) {
167
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
168
        } catch (\Exception $e) {
169
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
170
        } finally {
171
            $this->switchDomainBackIfNeeded($domain);
172
        }
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function checkCredentials($credentials, UserInterface $user)
179
    {
180
        $domain = $this->ldap->getDomainContext();
181
182
        try {
183
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
184
            $this->switchDomainIfNeeded($credDomain);
185
            /** @var \LdapTools\Operation\AuthenticationResponse $response */
186
            $response = $this->ldap->getConnection()->execute(
187
                new AuthenticationOperation(
188
                    $this->getBindUsername($user, $this->options['login_query_attribute']),
189
                    $credentials['password']
190
                )
191
            );
192 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...
193
                $this->userChecker->checkLdapErrorCode(
194
                    $user,
195
                    $response->getErrorCode(),
196
                    $this->ldap->getConnection()->getConfig()->getLdapType()
197
                );
198
                throw new CustomUserMessageAuthenticationException(
199
                    $response->getErrorMessage(), [], $response->getErrorCode()
200
                );
201
            }
202
            // No way to get the token from the Guard, need to create one to pass...
203
            $token = new UsernamePasswordToken($user, $credentials['password'], 'ldap-tools', $user->getRoles());
0 ignored issues
show
Documentation introduced by
$user->getRoles() is of type array<integer,object<Sym...Core\Role\Role>|string>, but the function expects a array<integer,object<Sym...\RoleInterface>|string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
204
            $token->setAttribute('ldap_domain', $credDomain);
205
            $this->dispatcher->dispatch(
206
                LdapLoginEvent::SUCCESS,
207
                new LdapLoginEvent($user, $token)
208
            );
209
        } catch (\Exception $e) {
210
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
211
        } finally {
212
            $this->domain = $this->ldap->getDomainContext();
213
            $this->switchDomainBackIfNeeded($domain);
214
        }
215
216
        return true;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 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...
223
    {
224
        $event = new AuthenticationHandlerEvent(
225
            $this->successHandler->onAuthenticationSuccess($request, $token),
226
            $request,
227
            null,
228
            $token,
229
            $providerKey
230
        );
231
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event);
232
233
        return $this->options['http_basic'] ? null : $event->getResponse();
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
240
    {
241
        $event = new AuthenticationHandlerEvent(
242
            $this->failureHandler->onAuthenticationFailure($request, $exception),
243
            $request,
244
            $exception
245
        );
246
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event);
247
248
        return $this->options['http_basic'] ? null : $event->getResponse();
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254 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...
255
    {
256
        $entryPoint = $this->options['http_basic'] ? new BasicAuthenticationEntryPoint($this->getHttpBasicRealm()) : $this->entryPoint;
257
        $event = new AuthenticationHandlerEvent(
258
            $entryPoint->start($request, $authException),
259
            $request,
260
            $authException
261
        );
262
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::START, $event);
263
264
        return $event->getResponse();
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function supportsRememberMe()
271
    {
272
        return $this->options['remember_me'];
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278
    public function createAuthenticatedToken(UserInterface $user, $providerKey)
279
    {
280
        $token = parent::createAuthenticatedToken($user, $providerKey);
281
        $token->setAttribute('ldap_domain', $this->domain);
282
283
        return $token;
284
    }
285
286
    /**
287
     * @param Request $request
288
     * @return null|string
289
     */
290
    protected function getRequestUsername(Request $request)
291
    {
292
        if ($this->options['http_basic']) {
293
            return $request->server->get('PHP_AUTH_USER');
294
        } else{
295
            return $this->getRequestParameter($this->options['username_parameter'], $request);
296
        }
297
    }
298
299
    /**
300
     * @param Request $request
301
     * @return null|string
302
     */
303
    protected function getRequestPassword(Request $request)
304
    {
305
        if ($this->options['http_basic']) {
306
            return $request->server->get('PHP_AUTH_PW');
307
        } else{
308
            return $this->getRequestParameter($this->options['password_parameter'], $request);
309
        }
310
    }
311
312
    /**
313
     * @param Request $request
314
     * @return null|string
315
     */
316
    protected function getRequestDomain(Request $request)
317
    {
318
        if ($this->options['http_basic']) {
319
            return $this->options['http_basic_domain'];
320
        } else{
321
            return $this->getRequestParameter($this->options['domain_parameter'], $request);
322
        }
323
    }
324
325
    /**
326
     * @param string $param
327
     * @param Request $request
328
     * @return string|null
329
     */
330
    protected function getRequestParameter($param, Request $request)
331
    {
332
        if ($this->options['post_only']) {
333
            $value = $request->request->get($param);
334
        } else {
335
            $value = $request->request->get($param) ?: $request->get($param);
336
        }
337
338
        return $value;
339
    }
340
341
    /**
342
     * @return string
343
     */
344
    protected function getHttpBasicRealm()
345
    {
346
        if ($this->options['http_basic_realm'] !== null) {
347
            $realm = $this->options['http_basic_realm'];
348
        } elseif ($this->options['http_basic_domain'] !== null) {
349
            $realm = $this->options['http_basic_domain'];
350
        } else {
351
            $realm = $this->ldap->getDomainContext();
352
        }
353
354
        return $realm;
355
    }
356
}
357