Completed
Push — master ( b7e5b9...80c0e8 )
by Chad
05:07
created

LdapGuardAuthenticator::getRequestDomain()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
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
     * {@inheritdoc}
118
     */
119
    public function getCredentials(Request $request)
120
    {
121
        $credentials = [
122
            'username' => $this->getRequestUsername($request),
123
            'password' => $this->getRequestPassword($request),
124
            'ldap_domain' => $this->getRequestDomain($request),
125
        ];
126
        if (empty($credentials['username'])) {
127
            return null;
128
        }
129
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']);
130
131
        return $credentials;
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function getUser($credentials, UserProviderInterface $userProvider)
138
    {
139
        $domain = $this->ldap->getDomainContext();
140
141
        try {
142
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
143
            $this->switchDomainIfNeeded($credDomain);
144
            $this->setLdapCredentialsIfNeeded($credentials['username'], $credentials['password'], $userProvider);
145
            $user = $userProvider->loadUserByUsername($credentials['username']);
146
            $this->userChecker->checkPreAuth($user);
147
148
            return $user;
149
        } catch (UsernameNotFoundException $e) {
150
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
151
        } catch (BadCredentialsException $e) {
152
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
153
        } catch (LdapConnectionException $e) {
154
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
155
        } catch (\Exception $e) {
156
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
157
        } finally {
158
            $this->switchDomainBackIfNeeded($domain);
159
        }
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function checkCredentials($credentials, UserInterface $user)
166
    {
167
        $domain = $this->ldap->getDomainContext();
168
169
        try {
170
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
171
            $this->switchDomainIfNeeded($credDomain);
172
            /** @var \LdapTools\Operation\AuthenticationResponse $response */
173
            $response = $this->ldap->getConnection()->execute(
174
                new AuthenticationOperation(
175
                    $this->getBindUsername($user, $this->options['login_query_attribute']),
176
                    $credentials['password']
177
                )
178
            );
179 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...
180
                $this->userChecker->checkLdapErrorCode(
181
                    $user,
182
                    $response->getErrorCode(),
183
                    $this->ldap->getConnection()->getConfig()->getLdapType()
184
                );
185
                throw new CustomUserMessageAuthenticationException(
186
                    $response->getErrorMessage(), [], $response->getErrorCode()
187
                );
188
            }
189
            // No way to get the token from the Guard, need to create one to pass...
190
            $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...
191
            $token->setAttribute('ldap_domain', $credDomain);
192
            $this->dispatcher->dispatch(
193
                LdapLoginEvent::SUCCESS,
194
                new LdapLoginEvent($user, $token)
195
            );
196
        } catch (\Exception $e) {
197
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
198
        } finally {
199
            $this->domain = $this->ldap->getDomainContext();
200
            $this->switchDomainBackIfNeeded($domain);
201
        }
202
203
        return true;
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209 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...
210
    {
211
        $event = new AuthenticationHandlerEvent(
212
            $this->successHandler->onAuthenticationSuccess($request, $token),
213
            $request,
214
            null,
215
            $token,
216
            $providerKey
217
        );
218
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event);
219
220
        return $this->options['http_basic'] ? null : $event->getResponse();
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
227
    {
228
        $event = new AuthenticationHandlerEvent(
229
            $this->failureHandler->onAuthenticationFailure($request, $exception),
230
            $request,
231
            $exception
232
        );
233
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event);
234
235
        return $this->options['http_basic'] ? null : $event->getResponse();
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 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...
242
    {
243
        $entryPoint = $this->options['http_basic'] ? new BasicAuthenticationEntryPoint($this->getHttpBasicRealm()) : $this->entryPoint;
244
        $event = new AuthenticationHandlerEvent(
245
            $entryPoint->start($request, $authException),
246
            $request,
247
            $authException
248
        );
249
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::START, $event);
250
251
        return $event->getResponse();
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function supportsRememberMe()
258
    {
259
        return $this->options['remember_me'];
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function createAuthenticatedToken(UserInterface $user, $providerKey)
266
    {
267
        $token = parent::createAuthenticatedToken($user, $providerKey);
268
        $token->setAttribute('ldap_domain', $this->domain);
269
270
        return $token;
271
    }
272
273
    /**
274
     * @param Request $request
275
     * @return null|string
276
     */
277
    protected function getRequestUsername(Request $request)
278
    {
279
        if ($this->options['http_basic']) {
280
            return $request->server->get('PHP_AUTH_USER');
281
        } else{
282
            return $this->getRequestParameter($this->options['username_parameter'], $request);
283
        }
284
    }
285
286
    /**
287
     * @param Request $request
288
     * @return null|string
289
     */
290
    protected function getRequestPassword(Request $request)
291
    {
292
        if ($this->options['http_basic']) {
293
            return $request->server->get('PHP_AUTH_PW');
294
        } else{
295
            return $this->getRequestParameter($this->options['password_parameter'], $request);
296
        }
297
    }
298
299
    /**
300
     * @param Request $request
301
     * @return null|string
302
     */
303
    protected function getRequestDomain(Request $request)
304
    {
305
        if ($this->options['http_basic']) {
306
            return $this->options['http_basic_domain'];
307
        } else{
308
            return $this->getRequestParameter($this->options['domain_parameter'], $request);
309
        }
310
    }
311
312
    /**
313
     * @param string $param
314
     * @param Request $request
315
     * @return string|null
316
     */
317
    protected function getRequestParameter($param, Request $request)
318
    {
319
        if ($this->options['post_only']) {
320
            $value = $request->request->get($param);
321
        } else {
322
            $value = $request->request->get($param) ?: $request->get($param);
323
        }
324
325
        return $value;
326
    }
327
328
    /**
329
     * @return string
330
     */
331
    protected function getHttpBasicRealm()
332
    {
333
        if ($this->options['http_basic_realm'] !== null) {
334
            $realm = $this->options['http_basic_realm'];
335
        } elseif ($this->options['http_basic_domain'] !== null) {
336
            $realm = $this->options['http_basic_domain'];
337
        } else {
338
            $realm = $this->ldap->getDomainContext();
339
        }
340
341
        return $realm;
342
    }
343
}
344