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\RedirectResponse; |
19
|
|
|
use Symfony\Component\HttpFoundation\Request; |
20
|
|
|
use Symfony\Component\Routing\RouterInterface; |
21
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
22
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; |
23
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
24
|
|
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException; |
25
|
|
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; |
26
|
|
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; |
27
|
|
|
use Symfony\Component\Security\Core\Security; |
28
|
|
|
use Symfony\Component\Security\Core\User\UserInterface; |
29
|
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface; |
30
|
|
|
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; |
31
|
|
|
use LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUserChecker; |
32
|
|
|
use LdapTools\Exception\LdapConnectionException; |
33
|
|
|
use LdapTools\LdapManager; |
34
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; |
35
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; |
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 bool |
51
|
|
|
*/ |
52
|
|
|
protected $hideUserNotFoundExceptions; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var LdapUserChecker |
56
|
|
|
*/ |
57
|
|
|
protected $userChecker; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var string |
61
|
|
|
*/ |
62
|
|
|
protected $domain; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var RouterInterface |
66
|
|
|
*/ |
67
|
|
|
protected $router; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @var EventDispatcherInterface |
71
|
|
|
*/ |
72
|
|
|
protected $dispatcher; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @var string The entry point/start path route name. |
76
|
|
|
*/ |
77
|
|
|
protected $startPath = 'login'; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var AuthenticationSuccessHandlerInterface |
81
|
|
|
*/ |
82
|
|
|
protected $successHandler; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @var AuthenticationFailureHandlerInterface |
86
|
|
|
*/ |
87
|
|
|
protected $failureHandler; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @param bool $hideUserNotFoundExceptions |
91
|
|
|
* @param LdapUserChecker $userChecker |
92
|
|
|
* @param LdapManager $ldap |
93
|
|
|
* @param RouterInterface $router |
94
|
|
|
* @param EventDispatcherInterface $dispatcher |
95
|
|
|
* @param AuthenticationSuccessHandlerInterface $successHandler |
96
|
|
|
* @param AuthenticationFailureHandlerInterface $failureHandler |
97
|
|
|
*/ |
98
|
|
|
public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker $userChecker, LdapManager $ldap, RouterInterface $router, EventDispatcherInterface $dispatcher, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler) |
99
|
|
|
{ |
100
|
|
|
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; |
101
|
|
|
$this->userChecker = $userChecker; |
102
|
|
|
$this->ldap = $ldap; |
103
|
|
|
$this->router = $router; |
104
|
|
|
$this->dispatcher = $dispatcher; |
105
|
|
|
$this->successHandler = $successHandler; |
106
|
|
|
$this->failureHandler = $failureHandler; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* {@inheritdoc} |
111
|
|
|
*/ |
112
|
|
|
public function getCredentials(Request $request) |
113
|
|
|
{ |
114
|
|
|
$credentials = [ |
115
|
|
|
'username' => '', |
116
|
|
|
'password' => '', |
117
|
|
|
'ldap_domain' => '', |
118
|
|
|
]; |
119
|
|
|
foreach (array_keys($credentials) as $key) { |
120
|
|
|
$param = "_$key"; |
121
|
|
|
$value = $request->request->get($param) ?: $request->get($param); |
122
|
|
|
$credentials[$key] = $value; |
123
|
|
|
} |
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()) { |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
public function start(Request $request, AuthenticationException $authException = null) |
235
|
|
|
{ |
236
|
|
|
return new RedirectResponse($this->router->generate($this->startPath)); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* {@inheritdoc} |
241
|
|
|
*/ |
242
|
|
|
public function supportsRememberMe() |
243
|
|
|
{ |
244
|
|
|
return false; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* {@inheritdoc} |
249
|
|
|
*/ |
250
|
|
|
public function createAuthenticatedToken(UserInterface $user, $providerKey) |
251
|
|
|
{ |
252
|
|
|
$token = parent::createAuthenticatedToken($user, $providerKey); |
253
|
|
|
$token->setAttribute('ldap_domain', $this->domain); |
254
|
|
|
|
255
|
|
|
return $token; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Set the entry point/start path. |
260
|
|
|
* |
261
|
|
|
* @param string $startPath |
262
|
|
|
*/ |
263
|
|
|
public function setStartPath($startPath) |
264
|
|
|
{ |
265
|
|
|
$this->startPath = $startPath; |
266
|
|
|
$this->configureAuthHandlers(); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* If the domain needs to a different context for the request, then switch it. |
271
|
|
|
* |
272
|
|
|
* @param array $credentials |
273
|
|
|
*/ |
274
|
|
|
protected function switchDomainIfNeeded($credentials) |
275
|
|
|
{ |
276
|
|
|
if (!empty($credentials['ldap_domain']) && $this->ldap->getDomainContext() !== $credentials['ldap_domain']) { |
277
|
|
|
$this->ldap->switchDomain($credentials['ldap_domain']); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* If the passed domain is not the current context, then switch back to it. |
283
|
|
|
* |
284
|
|
|
* @param string $domain |
285
|
|
|
*/ |
286
|
|
|
protected function switchDomainBackIfNeeded($domain) |
287
|
|
|
{ |
288
|
|
|
if ($domain !== $this->ldap->getDomainContext()) { |
289
|
|
|
$this->ldap->switchDomain($domain); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Determine whether or not the exception should be masked with a BadCredentials or not. |
295
|
|
|
* |
296
|
|
|
* @param \Exception $e |
297
|
|
|
* @throws \Exception |
298
|
|
|
*/ |
299
|
|
|
protected function hideOrThrow(\Exception $e) |
300
|
|
|
{ |
301
|
|
|
if ($this->hideUserNotFoundExceptions) { |
302
|
|
|
throw new BadCredentialsException('Bad credentials.', 0, $e); |
303
|
|
|
} |
304
|
|
|
// Specifically show LdapTools related exceptions, ignore others. |
305
|
|
|
if (!$this->hideUserNotFoundExceptions && $e instanceof Exception) { |
306
|
|
|
throw new CustomUserMessageAuthenticationException($e->getMessage(), [], $e->getCode()); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
throw $e; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Sets the start path based on the route name that was set and adds it to the default auth handlers. |
314
|
|
|
*/ |
315
|
|
|
protected function configureAuthHandlers() |
316
|
|
|
{ |
317
|
|
|
$loginPath = $this->router->generate($this->startPath); |
318
|
|
|
|
319
|
|
|
$options = $this->failureHandler->getOptions(); |
|
|
|
|
320
|
|
|
$options['login_path'] = $loginPath; |
321
|
|
|
$this->failureHandler->setOptions($options); |
|
|
|
|
322
|
|
|
|
323
|
|
|
$options = $this->successHandler->getOptions(); |
|
|
|
|
324
|
|
|
$options['login_path'] = $loginPath; |
325
|
|
|
$this->successHandler->setOptions($options); |
|
|
|
|
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
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.