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\Exception; |
18
|
|
|
use LdapTools\Exception\LdapConnectionException; |
19
|
|
|
use LdapTools\Operation\AuthenticationOperation; |
20
|
|
|
use LdapTools\LdapManager; |
21
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
22
|
|
|
use Symfony\Component\HttpFoundation\Request; |
23
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
24
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; |
25
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
26
|
|
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException; |
27
|
|
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; |
28
|
|
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; |
29
|
|
|
use Symfony\Component\Security\Core\Security; |
30
|
|
|
use Symfony\Component\Security\Core\User\UserInterface; |
31
|
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface; |
32
|
|
|
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; |
33
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; |
34
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; |
35
|
|
|
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* LDAP Guard Authenticator. |
39
|
|
|
* |
40
|
|
|
* @author Chad Sikorra <[email protected]> |
41
|
|
|
*/ |
42
|
|
|
class LdapGuardAuthenticator extends AbstractGuardAuthenticator |
43
|
|
|
{ |
44
|
|
|
/** |
45
|
|
|
* @var LdapManager |
46
|
|
|
*/ |
47
|
|
|
protected $ldap; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var LdapUserChecker |
51
|
|
|
*/ |
52
|
|
|
protected $userChecker; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string |
56
|
|
|
*/ |
57
|
|
|
protected $domain; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var EventDispatcherInterface |
61
|
|
|
*/ |
62
|
|
|
protected $dispatcher; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var AuthenticationSuccessHandlerInterface |
66
|
|
|
*/ |
67
|
|
|
protected $successHandler; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @var AuthenticationFailureHandlerInterface |
71
|
|
|
*/ |
72
|
|
|
protected $failureHandler; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @var AuthenticationEntryPointInterface |
76
|
|
|
*/ |
77
|
|
|
protected $entryPoint; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var array |
81
|
|
|
*/ |
82
|
|
|
protected $options = [ |
83
|
|
|
'hide_user_not_found_exceptions' => true, |
84
|
|
|
'username_parameter' => '_username', |
85
|
|
|
'password_parameter' => '_password', |
86
|
|
|
'domain_parameter' => '_ldap_domain', |
87
|
|
|
'post_only' => false, |
88
|
|
|
'remember_me' => false, |
89
|
|
|
]; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @param bool $hideUserNotFoundExceptions |
93
|
|
|
* @param LdapUserChecker $userChecker |
94
|
|
|
* @param LdapManager $ldap |
95
|
|
|
* @param AuthenticationEntryPointInterface $entryPoint |
96
|
|
|
* @param EventDispatcherInterface $dispatcher |
97
|
|
|
* @param AuthenticationSuccessHandlerInterface $successHandler |
98
|
|
|
* @param AuthenticationFailureHandlerInterface $failureHandler |
99
|
|
|
* @param array $options |
100
|
|
|
*/ |
101
|
|
|
public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker $userChecker, LdapManager $ldap, AuthenticationEntryPointInterface $entryPoint, EventDispatcherInterface $dispatcher, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) |
102
|
|
|
{ |
103
|
|
|
$this->userChecker = $userChecker; |
104
|
|
|
$this->ldap = $ldap; |
105
|
|
|
$this->entryPoint = $entryPoint; |
106
|
|
|
$this->dispatcher = $dispatcher; |
107
|
|
|
$this->successHandler = $successHandler; |
108
|
|
|
$this->failureHandler = $failureHandler; |
109
|
|
|
$this->options['hide_user_not_found_exceptions'] = $hideUserNotFoundExceptions; |
110
|
|
|
$this->options = array_merge($this->options, $options); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* {@inheritdoc} |
115
|
|
|
*/ |
116
|
|
|
public function getCredentials(Request $request) |
117
|
|
|
{ |
118
|
|
|
$credentials = [ |
119
|
|
|
'username' => $this->getRequestParameter($this->options['username_parameter'], $request), |
120
|
|
|
'password' => $this->getRequestParameter($this->options['password_parameter'], $request), |
121
|
|
|
'ldap_domain' => $this->getRequestParameter($this->options['domain_parameter'], $request), |
122
|
|
|
]; |
123
|
|
|
if (empty($credentials['username'])) { |
124
|
|
|
return null; |
125
|
|
|
} |
126
|
|
|
$request->getSession()->set(Security::LAST_USERNAME, $credentials['username']); |
127
|
|
|
|
128
|
|
|
return $credentials; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* {@inheritdoc} |
133
|
|
|
*/ |
134
|
|
|
public function getUser($credentials, UserProviderInterface $userProvider) |
135
|
|
|
{ |
136
|
|
|
$domain = $this->ldap->getDomainContext(); |
137
|
|
|
|
138
|
|
|
try { |
139
|
|
|
$this->switchDomainIfNeeded($credentials); |
140
|
|
|
$this->setLdapCredentialsIfNeeded($credentials, $userProvider); |
141
|
|
|
$user = $userProvider->loadUserByUsername($credentials['username']); |
142
|
|
|
$this->userChecker->checkPreAuth($user); |
143
|
|
|
|
144
|
|
|
return $user; |
145
|
|
|
} catch (UsernameNotFoundException $e) { |
146
|
|
|
$this->hideOrThrow($e); |
147
|
|
|
} catch (BadCredentialsException $e) { |
148
|
|
|
$this->hideOrThrow($e); |
149
|
|
|
} catch (LdapConnectionException $e) { |
150
|
|
|
$this->hideOrThrow($e); |
151
|
|
|
} catch (\Exception $e) { |
152
|
|
|
$this->hideOrThrow($e); |
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
|
|
|
$this->switchDomainIfNeeded($credentials); |
167
|
|
|
/** @var \LdapTools\Operation\AuthenticationResponse $response */ |
168
|
|
|
$response = $this->ldap->getConnection()->execute( |
169
|
|
|
new AuthenticationOperation($user->getUsername(), $credentials['password']) |
170
|
|
|
); |
171
|
|
View Code Duplication |
if (!$response->isAuthenticated()) { |
|
|
|
|
172
|
|
|
$this->userChecker->checkLdapErrorCode( |
173
|
|
|
$user, |
174
|
|
|
$response->getErrorCode(), |
175
|
|
|
$this->ldap->getConnection()->getConfig()->getLdapType() |
176
|
|
|
); |
177
|
|
|
throw new CustomUserMessageAuthenticationException( |
178
|
|
|
$response->getErrorMessage(), [], $response->getErrorCode() |
179
|
|
|
); |
180
|
|
|
} |
181
|
|
|
// No way to get the token from the Guard, need to create one to pass... |
182
|
|
|
$token = new UsernamePasswordToken($user, $credentials['password'], 'ldap-tools', $user->getRoles()); |
|
|
|
|
183
|
|
|
$token->setAttribute('ldap_domain', isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : ''); |
184
|
|
|
$this->dispatcher->dispatch( |
185
|
|
|
LdapLoginEvent::SUCCESS, |
186
|
|
|
new LdapLoginEvent($user, $token) |
187
|
|
|
); |
188
|
|
|
} catch (\Exception $e) { |
189
|
|
|
$this->hideOrThrow($e); |
190
|
|
|
} finally { |
191
|
|
|
$this->domain = $this->ldap->getDomainContext(); |
192
|
|
|
$this->switchDomainBackIfNeeded($domain); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return true; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* {@inheritdoc} |
200
|
|
|
*/ |
201
|
|
View Code Duplication |
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) |
|
|
|
|
202
|
|
|
{ |
203
|
|
|
$event = new AuthenticationHandlerEvent( |
204
|
|
|
$this->successHandler->onAuthenticationSuccess($request, $token), |
205
|
|
|
$request, |
206
|
|
|
null, |
207
|
|
|
$token, |
208
|
|
|
$providerKey |
209
|
|
|
); |
210
|
|
|
$this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event); |
211
|
|
|
|
212
|
|
|
return $event->getResponse(); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* {@inheritdoc} |
217
|
|
|
*/ |
218
|
|
View Code Duplication |
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) |
|
|
|
|
219
|
|
|
{ |
220
|
|
|
$event = new AuthenticationHandlerEvent( |
221
|
|
|
$this->failureHandler->onAuthenticationFailure($request, $exception), |
222
|
|
|
$request, |
223
|
|
|
$exception |
224
|
|
|
); |
225
|
|
|
$this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event); |
226
|
|
|
|
227
|
|
|
return $event->getResponse(); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* {@inheritdoc} |
232
|
|
|
*/ |
233
|
|
View Code Duplication |
public function start(Request $request, AuthenticationException $authException = null) |
|
|
|
|
234
|
|
|
{ |
235
|
|
|
$event = new AuthenticationHandlerEvent( |
236
|
|
|
$this->entryPoint->start($request, $authException), |
237
|
|
|
$request, |
238
|
|
|
$authException |
239
|
|
|
); |
240
|
|
|
$this->dispatcher->dispatch(AuthenticationHandlerEvent::START, $event); |
241
|
|
|
|
242
|
|
|
return $event->getResponse(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* {@inheritdoc} |
247
|
|
|
*/ |
248
|
|
|
public function supportsRememberMe() |
249
|
|
|
{ |
250
|
|
|
return $this->options['remember_me']; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* {@inheritdoc} |
255
|
|
|
*/ |
256
|
|
|
public function createAuthenticatedToken(UserInterface $user, $providerKey) |
257
|
|
|
{ |
258
|
|
|
$token = parent::createAuthenticatedToken($user, $providerKey); |
259
|
|
|
$token->setAttribute('ldap_domain', $this->domain); |
260
|
|
|
|
261
|
|
|
return $token; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param string $param |
266
|
|
|
* @param Request $request |
267
|
|
|
* @return string|null |
268
|
|
|
*/ |
269
|
|
|
protected function getRequestParameter($param, Request $request) |
270
|
|
|
{ |
271
|
|
|
if ($this->options['post_only']) { |
272
|
|
|
$value = $request->request->get($param); |
273
|
|
|
} else { |
274
|
|
|
$value = $request->request->get($param) ?: $request->get($param); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return $value; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* If no LDAP credentials are in the config then attempt to use the user supplied credentials from the login. But |
282
|
|
|
* only if we are using the LdapUserProvider. |
283
|
|
|
* |
284
|
|
|
* @param array $credentials |
285
|
|
|
* @param UserProviderInterface $userProvider |
286
|
|
|
*/ |
287
|
|
|
protected function setLdapCredentialsIfNeeded(array $credentials, UserProviderInterface $userProvider) |
288
|
|
|
{ |
289
|
|
|
// Only care about this in the context of the LDAP user provider... |
290
|
|
|
if (!$userProvider instanceof LdapUserProvider) { |
291
|
|
|
return; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// Only if the username/password are not defined in the config.... |
295
|
|
|
$config = $this->ldap->getConnection()->getConfig(); |
296
|
|
|
if (!(empty($config->getUsername()) && (empty($config->getPassword() && $config->getPassword() !== '0')))) { |
297
|
|
|
return; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
$config->setUsername($credentials['username']); |
301
|
|
|
$config->setPassword($credentials['password']); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* If the domain needs to a different context for the request, then switch it. |
306
|
|
|
* |
307
|
|
|
* @param array $credentials |
308
|
|
|
*/ |
309
|
|
|
protected function switchDomainIfNeeded(array $credentials) |
310
|
|
|
{ |
311
|
|
|
if (!empty($credentials['ldap_domain']) && $this->ldap->getDomainContext() !== $credentials['ldap_domain']) { |
312
|
|
|
$this->ldap->switchDomain($credentials['ldap_domain']); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* If the passed domain is not the current context, then switch back to it. |
318
|
|
|
* |
319
|
|
|
* @param string $domain |
320
|
|
|
*/ |
321
|
|
|
protected function switchDomainBackIfNeeded($domain) |
322
|
|
|
{ |
323
|
|
|
if ($domain !== $this->ldap->getDomainContext()) { |
324
|
|
|
$this->ldap->switchDomain($domain); |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Determine whether or not the exception should be masked with a BadCredentials or not. |
330
|
|
|
* |
331
|
|
|
* @param \Exception $e |
332
|
|
|
* @throws \Exception |
333
|
|
|
*/ |
334
|
|
|
protected function hideOrThrow(\Exception $e) |
335
|
|
|
{ |
336
|
|
|
if ($this->options['hide_user_not_found_exceptions']) { |
337
|
|
|
throw new BadCredentialsException('Bad credentials.', 0, $e); |
338
|
|
|
} |
339
|
|
|
// Specifically show LdapTools related exceptions, ignore others. |
340
|
|
|
if (!$this->options['hide_user_not_found_exceptions'] && $e instanceof Exception) { |
341
|
|
|
throw new CustomUserMessageAuthenticationException($e->getMessage(), [], $e->getCode()); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
throw $e; |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
|
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.