1 package org.bremersee.spring.security.ldaptive.authentication;
2
3 import static org.assertj.core.api.Assertions.assertThat;
4 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5 import static org.mockito.ArgumentMatchers.any;
6 import static org.mockito.ArgumentMatchers.anyString;
7 import static org.mockito.Mockito.doAnswer;
8 import static org.mockito.Mockito.doReturn;
9 import static org.mockito.Mockito.doThrow;
10 import static org.mockito.Mockito.mock;
11 import static org.mockito.Mockito.spy;
12 import static org.mockito.Mockito.verify;
13
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Optional;
17 import org.assertj.core.api.Assertions;
18 import org.assertj.core.api.SoftAssertions;
19 import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
20 import org.bremersee.ldaptive.DefaultLdaptiveErrorHandler;
21 import org.bremersee.ldaptive.LdaptiveException;
22 import org.bremersee.ldaptive.LdaptiveTemplate;
23 import org.bremersee.spring.security.ldaptive.authentication.provider.ActiveDirectoryTemplate;
24 import org.bremersee.spring.security.ldaptive.authentication.provider.OpenLdapTemplate;
25 import org.bremersee.spring.security.ldaptive.authentication.provider.UserContainsGroupsTemplate;
26 import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveUser;
27 import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveUserDetails;
28 import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveUserDetailsService;
29 import org.junit.jupiter.api.Test;
30 import org.junit.jupiter.api.extension.ExtendWith;
31 import org.ldaptive.CompareRequest;
32 import org.ldaptive.ConnectionConfig;
33 import org.ldaptive.ConnectionFactory;
34 import org.ldaptive.DefaultConnectionFactory;
35 import org.ldaptive.LdapAttribute;
36 import org.ldaptive.LdapEntry;
37 import org.ldaptive.LdapException;
38 import org.ldaptive.ResultCode;
39 import org.mockito.ArgumentCaptor;
40 import org.springframework.security.authentication.AccountExpiredException;
41 import org.springframework.security.authentication.BadCredentialsException;
42 import org.springframework.security.authentication.CredentialsExpiredException;
43 import org.springframework.security.authentication.DisabledException;
44 import org.springframework.security.authentication.LockedException;
45 import org.springframework.security.authentication.RememberMeAuthenticationToken;
46 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
47 import org.springframework.security.core.GrantedAuthority;
48 import org.springframework.security.core.authority.SimpleGrantedAuthority;
49 import org.springframework.security.core.userdetails.UsernameNotFoundException;
50 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
51 import org.springframework.security.crypto.password.PasswordEncoder;
52
53
54
55
56 @ExtendWith({SoftAssertionsExtension.class})
57 class LdaptiveAuthenticationManagerTest {
58
59 private static final String USER_BASE_DN = "ou=people,dc=bremersee,dc=org";
60
61 private static final String USER_DN = "uid=junit,ou=people,dc=bremersee,dc=org";
62
63 private static final String REMEMBER_ME_KEY = "bremersee";
64
65
66
67
68 @Test
69 void init() {
70 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
71 properties.setPasswordAttribute("userPassword");
72 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
73 new ConnectionConfig("ldap://localhost:389"),
74 properties,
75 REMEMBER_ME_KEY);
76 assertThatExceptionOfType(IllegalStateException.class)
77 .isThrownBy(target::init);
78
79 target.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
80 target.setEmailToUsernameResolver(null);
81 target.setUsernameToBindDnConverter(null);
82 target.setAccountControlEvaluator(null);
83 target.init();
84 }
85
86
87
88
89 @Test
90 void supports() {
91 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
92 mock(ConnectionFactory.class),
93 new OpenLdapTemplate(),
94 REMEMBER_ME_KEY);
95 target.init();
96 boolean actual = target.supports(UsernamePasswordAuthenticationToken.class);
97 assertThat(actual)
98 .isTrue();
99 }
100
101
102
103
104
105
106 @Test
107 void isRefusedUsername(SoftAssertions softly) {
108 OpenLdapTemplate properties = new OpenLdapTemplate();
109 properties.setRefusedUsernames(List.of("junit"));
110 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
111 mock(ConnectionFactory.class),
112 properties,
113 REMEMBER_ME_KEY);
114 target.init();
115 boolean actual = target.isRefusedUsername("junit");
116 softly.assertThat(actual).isTrue();actual = target.isRefusedUsername("");
117 softly.assertThat(actual).isTrue();
118 actual = target.isRefusedUsername("not-refused");
119 softly.assertThat(actual).isFalse();
120 }
121
122
123
124
125
126
127 @Test
128 void getLdaptiveTemplate(SoftAssertions softly) {
129 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
130 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
131 LdaptiveTemplate ldaptiveTemplate = new LdaptiveTemplate(connectionFactory);
132 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
133 ldaptiveTemplate,
134 new ActiveDirectoryTemplate(),
135 REMEMBER_ME_KEY);
136 target.setUsernameToBindDnConverter(username -> username);
137 target.init();
138 softly
139 .assertThat(target.getApplicationLdaptiveTemplate())
140 .isEqualTo(ldaptiveTemplate);
141 softly
142 .assertThat(target.getLdapTemplate("junit", "secret"))
143 .isNotNull();
144 }
145
146
147
148
149 @Test
150 void getUserDetailsService() {
151 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
152 mock(ConnectionFactory.class),
153 new OpenLdapTemplate(),
154 REMEMBER_ME_KEY);
155 assertThat(target.getUserDetailsService())
156 .isNotNull();
157 }
158
159
160
161
162
163
164 @Test
165 void authenticateWithApplicationLdaptiveTemplate(SoftAssertions softly) {
166 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
167 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
168 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
169 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
170 properties.setUserBaseDn(USER_BASE_DN);
171 properties.setPasswordAttribute("userPassword");
172 properties.setAccountControlEvaluator(null);
173 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
174 ldaptiveTemplate, properties,
175 REMEMBER_ME_KEY));
176 target.setTokenConverter(LdaptiveAuthenticationToken::new);
177
178 PasswordEncoder passwordEncoder = mock(PasswordEncoder.class);
179 doAnswer(invocationOnMock -> invocationOnMock.getArgument(0))
180 .when(passwordEncoder)
181 .encode(anyString());
182 target.setPasswordEncoder(passwordEncoder);
183
184 target.init();
185
186 LdapEntry user = createUser();
187 doReturn(Optional.of(user))
188 .when(ldaptiveTemplate)
189 .findOne(any());
190 ArgumentCaptor<CompareRequest> compareCaptor = ArgumentCaptor.forClass(CompareRequest.class);
191 doReturn(true)
192 .when(ldaptiveTemplate)
193 .compare(compareCaptor.capture());
194
195 LdaptiveAuthentication actual = target
196 .authenticate(new UsernamePasswordAuthenticationToken("junit", "secret"));
197
198 softly
199 .assertThat(compareCaptor.getValue())
200 .isNotNull();
201 verify(target)
202 .checkAccountControl(any());
203 assertActual(actual, softly);
204 }
205
206
207
208
209 @Test
210 void authenticateWithApplicationLdaptiveTemplateAndBadCredentials() {
211 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
212 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
213 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
214 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
215 properties.setUserBaseDn(USER_BASE_DN);
216 properties.setPasswordAttribute("userPassword");
217 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
218 ldaptiveTemplate, properties,
219 REMEMBER_ME_KEY);
220
221 PasswordEncoder passwordEncoder = mock(PasswordEncoder.class);
222 doAnswer(invocationOnMock -> invocationOnMock.getArgument(0))
223 .when(passwordEncoder)
224 .encode(anyString());
225 target.setPasswordEncoder(passwordEncoder);
226
227 target.init();
228
229 LdapEntry user = createUser();
230 doReturn(Optional.of(user))
231 .when(ldaptiveTemplate)
232 .findOne(any());
233 doReturn(false)
234 .when(ldaptiveTemplate)
235 .compare(any());
236
237 var token = new UsernamePasswordAuthenticationToken("junit", "secret");
238 assertThatExceptionOfType(BadCredentialsException.class)
239 .isThrownBy(() -> target.authenticate(token));
240 }
241
242
243
244
245
246
247 @Test
248 void authenticateWithUserLdaptiveTemplate(SoftAssertions softly) {
249 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
250 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
251 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
252 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
253 properties.setUserBaseDn(USER_BASE_DN);
254 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
255 ldaptiveTemplate, properties, REMEMBER_ME_KEY));
256
257 target.init();
258
259 doReturn(ldaptiveTemplate)
260 .when(target)
261 .getLdapTemplate("junit", "secret");
262
263 LdapEntry user = createUser();
264 doReturn(Optional.of(user))
265 .when(ldaptiveTemplate)
266 .findOne(any());
267
268 LdaptiveAuthentication actual = target
269 .authenticate(new UsernamePasswordAuthenticationToken("junit", "secret"));
270
271 verify(target)
272 .checkAccountControl(any());
273 assertActual(actual, softly);
274 }
275
276
277
278
279 @Test
280 void authenticateWithUserLdaptiveTemplateAndUsernameNotFound() {
281 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
282 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
283 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
284 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
285 properties.setUserBaseDn(USER_BASE_DN);
286 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
287 ldaptiveTemplate, properties, REMEMBER_ME_KEY));
288
289 target.init();
290
291 doReturn(ldaptiveTemplate)
292 .when(target)
293 .getLdapTemplate("junit", "secret");
294 LdaptiveUserDetailsService userDetailsService = mock(LdaptiveUserDetailsService.class);
295 doThrow(new UsernameNotFoundException("junit not found"))
296 .when(userDetailsService)
297 .loadUserByUsername(anyString());
298 doReturn(userDetailsService)
299 .when(target)
300 .getUserDetailsService(any());
301
302 var token = new UsernamePasswordAuthenticationToken("junit", "secret");
303 assertThatExceptionOfType(UsernameNotFoundException.class)
304 .isThrownBy(() -> target.authenticate(token));
305 }
306
307
308
309
310 @Test
311 void authenticateWithUserLdaptiveTemplateAndBadCredentialsWithResultCode() {
312 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
313 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
314 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
315 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
316 properties.setUserBaseDn(USER_BASE_DN);
317 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
318 ldaptiveTemplate, properties, REMEMBER_ME_KEY));
319
320 target.init();
321
322 doReturn(ldaptiveTemplate)
323 .when(target)
324 .getLdapTemplate("junit", "secret");
325 LdaptiveException ldaptiveException = new DefaultLdaptiveErrorHandler()
326 .map(new LdapException(ResultCode.INVALID_CREDENTIALS, "Bad credentials"));
327 doThrow(ldaptiveException)
328 .when(ldaptiveTemplate)
329 .findOne(any());
330
331 var token = new UsernamePasswordAuthenticationToken("junit", "secret");
332 assertThatExceptionOfType(BadCredentialsException.class)
333 .isThrownBy(() -> target.authenticate(token));
334 }
335
336
337
338
339 @Test
340 void authenticateWithUserLdaptiveTemplateAndBadCredentialsWithMessage() {
341 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
342 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
343 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
344 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
345 properties.setUserBaseDn(USER_BASE_DN);
346 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
347 ldaptiveTemplate, properties, REMEMBER_ME_KEY));
348
349 target.init();
350
351 doReturn(ldaptiveTemplate)
352 .when(target)
353 .getLdapTemplate("junit", "secret");
354 LdaptiveException ldaptiveException = new DefaultLdaptiveErrorHandler()
355 .map(new LdapException(ResultCode.CONNECT_ERROR,
356 "resultCode=" + ResultCode.INVALID_CREDENTIALS));
357 doThrow(ldaptiveException)
358 .when(ldaptiveTemplate)
359 .findOne(any());
360
361 var token = new UsernamePasswordAuthenticationToken("junit", "secret");
362 assertThatExceptionOfType(BadCredentialsException.class)
363 .isThrownBy(() -> target.authenticate(token));
364 }
365
366
367
368
369 @Test
370 void authenticateWithUserLdaptiveTemplateAndRefusedUsername() {
371 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
372 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
373 LdaptiveTemplate ldaptiveTemplate = spy(new LdaptiveTemplate(connectionFactory));
374 ActiveDirectoryTemplate properties = new ActiveDirectoryTemplate();
375 properties.setUserBaseDn(USER_BASE_DN);
376 properties.setRefusedUsernames(List.of("refused"));
377 LdaptiveAuthenticationManager target = spy(new LdaptiveAuthenticationManager(
378 ldaptiveTemplate, properties, REMEMBER_ME_KEY));
379
380 target.init();
381
382 var token = new UsernamePasswordAuthenticationToken("refused", "secret");
383 assertThatExceptionOfType(DisabledException.class)
384 .isThrownBy(() -> target.authenticate(token));
385 }
386
387
388
389
390
391
392 @Test
393 void checkAccountControl(SoftAssertions softly) {
394 ConnectionConfig connectionConfig = new ConnectionConfig("ldap://localhost:389");
395 ConnectionFactory connectionFactory = new DefaultConnectionFactory(connectionConfig);
396 LdaptiveTemplate ldaptiveTemplate = new LdaptiveTemplate(connectionFactory);
397 UserContainsGroupsTemplate properties = new UserContainsGroupsTemplate();
398 properties.setUserBaseDn(USER_BASE_DN);
399 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
400 ldaptiveTemplate, properties, REMEMBER_ME_KEY);
401
402 LdapEntry user = createUser();
403
404 LdaptiveUserDetails d0 = new LdaptiveUser(
405 user, "junit", null, null, null,
406 List.of(new SimpleGrantedAuthority("ROLE_tester")),
407 "secret",
408 false, true, true, true);
409 softly.assertThatExceptionOfType(AccountExpiredException.class)
410 .isThrownBy(() -> target.checkAccountControl(d0));
411
412 LdaptiveUserDetails d1 = new LdaptiveUser(
413 user, "junit", null, null, null,
414 List.of(new SimpleGrantedAuthority("ROLE_tester")),
415 "secret",
416 true, false, true, true);
417 softly.assertThatExceptionOfType(LockedException.class)
418 .isThrownBy(() -> target.checkAccountControl(d1));
419
420 LdaptiveUserDetails d2 = new LdaptiveUser(
421 user, "junit", null, null, null,
422 List.of(new SimpleGrantedAuthority("ROLE_tester")),
423 "secret",
424 true, true, false, true);
425 softly.assertThatExceptionOfType(CredentialsExpiredException.class)
426 .isThrownBy(() -> target.checkAccountControl(d2));
427
428 LdaptiveUserDetails d3 = new LdaptiveUser(
429 user, "junit", null, null, null,
430 List.of(new SimpleGrantedAuthority("ROLE_tester")),
431 "secret",
432 true, true, true, false);
433 softly.assertThatExceptionOfType(DisabledException.class)
434 .isThrownBy(() -> target.checkAccountControl(d3));
435 }
436
437 private void assertActual(LdaptiveAuthentication authentication, SoftAssertions softly) {
438 List<GrantedAuthority> actualAuthorities = new ArrayList<>(authentication.getAuthorities());
439 List<GrantedAuthority> expectedAuthorities = List.of(new SimpleGrantedAuthority("ROLE_tester"));
440 softly
441 .assertThat(actualAuthorities)
442 .containsExactlyInAnyOrderElementsOf(expectedAuthorities);
443 softly
444 .assertThat(authentication.getName())
445 .isEqualTo("junit");
446 softly
447 .assertThat(authentication.getPrincipal().getFirstName())
448 .isEqualTo("Test");
449 softly
450 .assertThat(authentication.getPrincipal().getLastName())
451 .isEqualTo("User");
452 softly
453 .assertThat(authentication.getPrincipal().getEmail())
454 .isEqualTo("junit@example.com");
455
456 assertThat(authentication.getPrincipal())
457 .isInstanceOf(LdaptiveUserDetails.class);
458 LdaptiveUserDetails principal = authentication.getPrincipal();
459 softly
460 .assertThat(principal.getDn())
461 .isEqualTo(USER_DN);
462 softly
463 .assertThat(principal.getName())
464 .isEqualTo("junit");
465 softly
466 .assertThat(principal.getName())
467 .isEqualTo("junit");
468 softly
469 .assertThat(principal.getUsername())
470 .isEqualTo("junit");
471 softly
472 .assertThat(principal.getPassword())
473 .isNotEmpty();
474 softly
475 .assertThat(principal.isEnabled())
476 .isTrue();
477 softly
478 .assertThat(principal.isAccountNonLocked())
479 .isTrue();
480 softly
481 .assertThat(principal.isAccountNonExpired())
482 .isTrue();
483 softly
484 .assertThat(principal.isCredentialsNonExpired())
485 .isTrue();
486 }
487
488
489
490
491 @Test
492 void rememberMeAuthenticationThrowsBadCredentialsExceptionWithInvalidKey() {
493 LdaptiveAuthenticationManager target = new LdaptiveAuthenticationManager(
494 mock(ConnectionFactory.class),
495 new OpenLdapTemplate(),
496 REMEMBER_ME_KEY);
497
498 RememberMeAuthenticationToken token = mock(RememberMeAuthenticationToken.class);
499 doReturn(0)
500 .when(token)
501 .getKeyHash();
502
503 Assertions.assertThatExceptionOfType(BadCredentialsException.class)
504 .isThrownBy(() -> target.authenticate(token));
505 }
506
507 private LdapEntry createUser() {
508 LdapEntry entry = new LdapEntry();
509 entry.setDn(USER_DN);
510 entry.addAttributes(LdapAttribute.builder().name("uid").values("junit").build());
511 entry.addAttributes(LdapAttribute.builder().name("givenName").values("Test").build());
512 entry.addAttributes(LdapAttribute.builder().name("sn").values("User").build());
513 entry.addAttributes(LdapAttribute.builder().name("mail").values("junit@example.com").build());
514 entry.addAttributes(LdapAttribute.builder()
515 .name("memberOf").values("cn=tester," + USER_BASE_DN).build());
516 return entry;
517 }
518
519 }