Completed
Push — master ( 19b194...cab368 )
by Chad
01:27
created

LdapGuardAuthenticator::switchDomainIfNeeded()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 3
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
36
/**
37
 * LDAP Guard Authenticator.
38
 *
39
 * @author Chad Sikorra <[email protected]>
40
 */
41
class LdapGuardAuthenticator extends AbstractGuardAuthenticator
42
{
43
    use LdapAuthenticationTrait;
44
45
    /**
46
     * @var LdapUserChecker
47
     */
48
    protected $userChecker;
49
50
    /**
51
     * @var string
52
     */
53
    protected $domain;
54
55
    /**
56
     * @var EventDispatcherInterface
57
     */
58
    protected $dispatcher;
59
60
    /**
61
     * @var AuthenticationSuccessHandlerInterface
62
     */
63
    protected $successHandler;
64
65
    /**
66
     * @var AuthenticationFailureHandlerInterface
67
     */
68
    protected $failureHandler;
69
70
    /**
71
     * @var AuthenticationEntryPointInterface
72
     */
73
    protected $entryPoint;
74
75
    /**
76
     * @var array
77
     */
78
    protected $options = [
79
        'hide_user_not_found_exceptions' => true,
80
        'username_parameter' => '_username',
81
        'password_parameter' => '_password',
82
        'domain_parameter' => '_ldap_domain',
83
        'post_only' => false,
84
        'remember_me' => false,
85
        'login_query_attribute' => null,
86
    ];
87
88
    /**
89
     * @param bool $hideUserNotFoundExceptions
90
     * @param LdapUserChecker $userChecker
91
     * @param LdapManager $ldap
92
     * @param AuthenticationEntryPointInterface $entryPoint
93
     * @param EventDispatcherInterface $dispatcher
94
     * @param AuthenticationSuccessHandlerInterface $successHandler
95
     * @param AuthenticationFailureHandlerInterface $failureHandler
96
     * @param array $options
97
     * @param LdapUserProvider $ldapUserProvider
98
     */
99
    public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker $userChecker, LdapManager $ldap, AuthenticationEntryPointInterface $entryPoint, EventDispatcherInterface $dispatcher, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options, LdapUserProvider $ldapUserProvider)
100
    {
101
        $this->userChecker = $userChecker;
102
        $this->ldap = $ldap;
103
        $this->entryPoint = $entryPoint;
104
        $this->dispatcher = $dispatcher;
105
        $this->successHandler = $successHandler;
106
        $this->failureHandler = $failureHandler;
107
        $this->options['hide_user_not_found_exceptions'] = $hideUserNotFoundExceptions;
108
        $this->ldapUserProvider = $ldapUserProvider;
109
        $this->options = array_merge($this->options, $options);
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function getCredentials(Request $request)
116
    {
117
        $credentials = [
118
            'username' => $this->getRequestParameter($this->options['username_parameter'], $request),
119
            'password' => $this->getRequestParameter($this->options['password_parameter'], $request),
120
            'ldap_domain' => $this->getRequestParameter($this->options['domain_parameter'], $request),
121
        ];
122
        if (empty($credentials['username'])) {
123
            return null;
124
        }
125
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']);
126
127
        return $credentials;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function getUser($credentials, UserProviderInterface $userProvider)
134
    {
135
        $domain = $this->ldap->getDomainContext();
136
137
        try {
138
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
139
            $this->switchDomainIfNeeded($credDomain);
140
            $this->setLdapCredentialsIfNeeded($credentials['username'], $credentials['password'], $userProvider);
141
            $user = $userProvider->loadUserByUsername($credentials['username']);
142
            $this->userChecker->checkPreAuth($user);
143
144
            return $user;
145
        } catch (UsernameNotFoundException $e) {
146
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
147
        } catch (BadCredentialsException $e) {
148
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
149
        } catch (LdapConnectionException $e) {
150
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
151
        } catch (\Exception $e) {
152
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
153
        } finally {
154
            $this->switchDomainBackIfNeeded($domain);
155
        }
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function checkCredentials($credentials, UserInterface $user)
162
    {
163
        $domain = $this->ldap->getDomainContext();
164
165
        try {
166
            $credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
167
            $this->switchDomainIfNeeded($credDomain);
168
            /** @var \LdapTools\Operation\AuthenticationResponse $response */
169
            $response = $this->ldap->getConnection()->execute(
170
                new AuthenticationOperation(
171
                    $this->getBindUsername($user, $this->options['login_query_attribute']),
172
                    $credentials['password']
173
                )
174
            );
175 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...
176
                $this->userChecker->checkLdapErrorCode(
177
                    $user,
178
                    $response->getErrorCode(),
179
                    $this->ldap->getConnection()->getConfig()->getLdapType()
180
                );
181
                throw new CustomUserMessageAuthenticationException(
182
                    $response->getErrorMessage(), [], $response->getErrorCode()
183
                );
184
            }
185
            // No way to get the token from the Guard, need to create one to pass...
186
            $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...
187
            $token->setAttribute('ldap_domain', $credDomain);
188
            $this->dispatcher->dispatch(
189
                LdapLoginEvent::SUCCESS,
190
                new LdapLoginEvent($user, $token)
191
            );
192
        } catch (\Exception $e) {
193
            $this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
194
        } finally {
195
            $this->domain = $this->ldap->getDomainContext();
196
            $this->switchDomainBackIfNeeded($domain);
197
        }
198
199
        return true;
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 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...
206
    {
207
        $event = new AuthenticationHandlerEvent(
208
            $this->successHandler->onAuthenticationSuccess($request, $token),
209
            $request,
210
            null,
211
            $token,
212
            $providerKey
213
        );
214
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event);
215
216
        return $event->getResponse();
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 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...
223
    {
224
        $event = new AuthenticationHandlerEvent(
225
            $this->failureHandler->onAuthenticationFailure($request, $exception),
226
            $request,
227
            $exception
228
        );
229
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event);
230
231
        return $event->getResponse();
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237 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...
238
    {
239
        $event = new AuthenticationHandlerEvent(
240
            $this->entryPoint->start($request, $authException),
241
            $request,
242
            $authException
243
        );
244
        $this->dispatcher->dispatch(AuthenticationHandlerEvent::START, $event);
245
246
        return $event->getResponse();
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function supportsRememberMe()
253
    {
254
        return $this->options['remember_me'];
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function createAuthenticatedToken(UserInterface $user, $providerKey)
261
    {
262
        $token = parent::createAuthenticatedToken($user, $providerKey);
263
        $token->setAttribute('ldap_domain', $this->domain);
264
265
        return $token;
266
    }
267
268
    /**
269
     * @param string $param
270
     * @param Request $request
271
     * @return string|null
272
     */
273
    protected function getRequestParameter($param, Request $request)
274
    {
275
        if ($this->options['post_only']) {
276
            $value = $request->request->get($param);
277
        } else {
278
            $value = $request->request->get($param) ?: $request->get($param);
279
        }
280
281
        return $value;
282
    }
283
}
284