View Javadoc
1   /*
2   * Copyright 2019-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.ldaptive;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.LinkedHashSet;
29  import java.util.Optional;
30  import java.util.Set;
31  import java.util.UUID;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.bremersee.exception.ServiceException;
35  import org.bremersee.ldaptive.app.Group;
36  import org.bremersee.ldaptive.app.GroupMapper;
37  import org.bremersee.ldaptive.app.Person;
38  import org.bremersee.ldaptive.app.PersonMapper;
39  import org.bremersee.ldaptive.app.TestConfiguration;
40  import org.junit.jupiter.api.BeforeAll;
41  import org.junit.jupiter.api.Test;
42  import org.ldaptive.AddRequest;
43  import org.ldaptive.CompareRequest;
44  import org.ldaptive.DeleteRequest;
45  import org.ldaptive.FilterTemplate;
46  import org.ldaptive.LdapAttribute;
47  import org.ldaptive.LdapEntry;
48  import org.ldaptive.ModifyDnRequest;
49  import org.ldaptive.ModifyRequest;
50  import org.ldaptive.SearchRequest;
51  import org.ldaptive.SearchScope;
52  import org.ldaptive.SimpleBindRequest;
53  import org.springframework.beans.factory.annotation.Autowired;
54  import org.springframework.beans.factory.annotation.Value;
55  import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapProperties;
56  import org.springframework.boot.test.context.SpringBootTest;
57  import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
58  import org.springframework.test.util.TestSocketUtils;
59  
60  /**
61   * The ldaptive template test.
62   *
63   * @author Christian Bremer
64   */
65  @SpringBootTest(
66      classes = {TestConfiguration.class},
67      webEnvironment = WebEnvironment.NONE,
68      properties = {
69          "spring.ldap.embedded.base-dn=dc=bremersee,dc=org",
70          "spring.ldap.embedded.credential.username=uid=admin",
71          "spring.ldap.embedded.credential.password=secret",
72          "spring.ldap.embedded.ldif=classpath:schema.ldif",
73          "spring.ldap.embedded.validation.enabled=false"
74      })
75  class LdaptiveTemplateTest {
76  
77    EmbeddedLdapProperties k;
78  
79    private static final Log log = LogFactory.getLog(LdaptiveTemplateTest.class);
80  
81    @Value("${spring.ldap.embedded.base-dn}")
82    private String baseDn;
83  
84    @Autowired
85    private LdaptiveTemplate ldaptiveTemplate;
86  
87    @Autowired
88    private PersonMapper personMapper;
89  
90    @Autowired
91    private GroupMapper groupMapper;
92  
93    /**
94     * Sets embedded ldap port.
95     */
96    @BeforeAll
97    static void setEmbeddedLdapPort() {
98      int embeddedLdapPort = TestSocketUtils.findAvailableTcpPort();
99      System.setProperty("spring.ldap.embedded.port", String.valueOf(embeddedLdapPort));
100   }
101 
102   /**
103    * Find existing persons.
104    */
105   @Test
106   void findExistingPersons() {
107     SearchRequest searchRequest = SearchRequest.builder()
108         .dn("ou=people," + baseDn)
109         .filter("(objectclass=inetOrgPerson)")
110         .scope(SearchScope.ONELEVEL)
111         .build();
112 
113     // without mapper
114     Collection<LdapEntry> entries = ldaptiveTemplate.findAll(searchRequest);
115     entries.forEach(ldapEntry -> log.info(String.format("Ldap entry found with cn = %s",
116         ldapEntry.getAttribute("cn").getStringValue())));
117     assertTrue(entries.stream()
118         .anyMatch(entry -> "Anna Livia Plurabelle"
119             .equalsIgnoreCase(entry.getAttribute("cn").getStringValue())));
120     assertTrue(entries.stream()
121         .anyMatch(entry -> "Gustav Anias Horn"
122             .equalsIgnoreCase(entry.getAttribute("cn").getStringValue())));
123     assertTrue(entries.stream()
124         .anyMatch(entry -> "Hans Castorp"
125             .equalsIgnoreCase(entry.getAttribute("cn").getStringValue())));
126 
127     // with mapper
128     assertTrue(ldaptiveTemplate.findAll(searchRequest, personMapper)
129         .anyMatch(entry -> "Anna Livia Plurabelle"
130             .equalsIgnoreCase(entry.getCn())));
131     assertTrue(ldaptiveTemplate.findAll(searchRequest, personMapper)
132         .anyMatch(entry -> "Gustav Anias Horn"
133             .equalsIgnoreCase(entry.getCn())));
134     assertTrue(ldaptiveTemplate.findAll(searchRequest, personMapper)
135         .anyMatch(entry -> "Hans Castorp"
136             .equalsIgnoreCase(entry.getCn())));
137   }
138 
139   /**
140    * Find existing groups.
141    */
142   @Test
143   void findExistingGroups() {
144     SearchRequest searchRequest = SearchRequest.builder()
145         .dn("ou=groups," + baseDn)
146         .filter("(objectclass=groupOfUniqueNames)")
147         .scope(SearchScope.ONELEVEL)
148         .build();
149 
150     // without mapper
151     Collection<LdapEntry> entries = ldaptiveTemplate.findAll(searchRequest);
152     entries.forEach(ldapEntry -> log.info(String.format("Ldap entry found with cn = %s",
153         ldapEntry.getAttribute("cn").getStringValue())));
154     assertTrue(entries.stream()
155         .anyMatch(entry -> "developers"
156             .equalsIgnoreCase(entry.getAttribute("cn").getStringValue())));
157     assertTrue(entries.stream()
158         .anyMatch(entry -> "managers"
159             .equalsIgnoreCase(entry.getAttribute("cn").getStringValue())));
160 
161     // with mapper
162     assertTrue(ldaptiveTemplate.findAll(searchRequest, groupMapper)
163         .anyMatch(entry -> "developers"
164             .equalsIgnoreCase(entry.getCn())));
165     assertTrue(ldaptiveTemplate.findAll(searchRequest, groupMapper)
166         .anyMatch(entry -> "managers"
167             .equalsIgnoreCase(entry.getCn())));
168   }
169 
170   /**
171    * Find one existing person.
172    */
173   @Test
174   void findOneAndExpectSizeLimitExceeded() {
175     SearchRequest searchRequest = SearchRequest.builder()
176         .dn("ou=people," + baseDn)
177         .filter(FilterTemplate.builder()
178             .filter("(objectclass=inetOrgPerson)")
179             .build())
180         .scope(SearchScope.ONELEVEL)
181         .sizeLimit(1)
182         .build();
183 
184     assertThrows(LdaptiveException.class, () -> ldaptiveTemplate.findOne(searchRequest));
185   }
186 
187   /**
188    * Find existing person.
189    */
190   @Test
191   void findExistingPerson() {
192     SearchRequest searchRequest = SearchRequest.builder()
193         .dn("ou=people," + baseDn)
194         .filter(FilterTemplate.builder()
195             .filter("(&(objectclass=inetOrgPerson)(uid={0}))")
196             .parameters("anna")
197             .build())
198         .scope(SearchScope.ONELEVEL)
199         .sizeLimit(1)
200         .build();
201 
202     Optional<LdapEntry> entry = ldaptiveTemplate.findOne(searchRequest);
203     assertTrue(entry.isPresent());
204     assertEquals("anna", entry.get().getAttribute("uid").getStringValue());
205 
206     Optional<Person> person = ldaptiveTemplate.findOne(searchRequest, personMapper);
207     assertTrue(person.isPresent());
208     assertEquals("anna", person.get().getUid());
209   }
210 
211   /**
212    * Find existing person by dn.
213    */
214   @Test
215   void findExistingPersonByDn() {
216     String dn = "uid=anna,ou=people," + baseDn;
217     Optional<LdapEntry> entry = ldaptiveTemplate
218         .findOne(SearchRequest.objectScopeSearchRequest(dn));
219     assertTrue(entry.isPresent());
220   }
221 
222   /**
223    * Find non existing person.
224    */
225   @Test
226   void findNonExistingPerson() {
227     SearchRequest searchRequest = SearchRequest.builder()
228         .dn("ou=people," + baseDn)
229         .filter(FilterTemplate.builder()
230             .filter("(&(objectclass=inetOrgPerson)(uid={0}))")
231             .parameters(UUID.randomUUID().toString().replace("-", ""))
232             .build())
233         .scope(SearchScope.ONELEVEL)
234         .build();
235 
236     Optional<LdapEntry> entry = ldaptiveTemplate.findOne(searchRequest);
237     assertFalse(entry.isPresent());
238   }
239 
240   /**
241    * Find non existing person by dn.
242    */
243   @Test
244   void findNonExistingPersonByDn() {
245     String uid = UUID.randomUUID().toString().replace("-", "");
246     String dn = "uid=" + uid + ",ou=people," + baseDn;
247     Optional<LdapEntry> entry = ldaptiveTemplate
248         .findOne(SearchRequest.objectScopeSearchRequest(dn));
249     assertFalse(entry.isPresent());
250   }
251 
252   /**
253    * Generate user password and bind.
254    */
255   @Test
256   void generateUserPasswordAndBind() {
257     String dn = "uid=anna,ou=people," + baseDn;
258 
259     String initPasswd = ldaptiveTemplate.generateUserPassword(dn);
260     assertNotNull(initPasswd);
261 
262     String newPasswd = initPasswd + "Pass1234";
263     ldaptiveTemplate.modifyUserPassword(dn, initPasswd, newPasswd);
264 
265     boolean success = ldaptiveTemplate.bind(SimpleBindRequest.builder()
266         .dn(dn)
267         .password(newPasswd)
268         .build());
269     assertTrue(success);
270 
271     success = ldaptiveTemplate.bind(SimpleBindRequest.builder()
272         .dn(dn)
273         .password(initPasswd + "f")
274         .build());
275     assertFalse(success);
276   }
277 
278   /**
279    * Set user password fails.
280    */
281   @Test
282   void setUserPasswordFails() {
283     String dn = "uid=hans,ou=people," + baseDn;
284 
285     String initPasswd = ldaptiveTemplate.generateUserPassword(dn);
286     assertNotNull(initPasswd);
287 
288     String newPasswd = initPasswd + "Pass1234";
289     assertThrows(ServiceException.class, () -> ldaptiveTemplate
290         .modifyUserPassword(dn, "wrong", newPasswd));
291   }
292 
293   /**
294    * Exists group.
295    */
296   @Test
297   void existsGroup() {
298     Group group = new Group();
299     group.setCn("developers");
300     assertTrue(ldaptiveTemplate.exists(group, groupMapper));
301     group.setCn("na");
302     assertFalse(ldaptiveTemplate.exists(group, groupMapper));
303   }
304 
305   /**
306    * Compare group.
307    */
308   @Test
309   void compareGroup() {
310     Group group = new Group();
311     group.setCn("developers");
312 
313     assertTrue(ldaptiveTemplate.compare(CompareRequest.builder()
314         .dn(groupMapper.mapDn(group))
315         .name("ou")
316         .value("developer")
317         .build()));
318 
319     assertFalse(ldaptiveTemplate.compare(CompareRequest.builder()
320         .dn(groupMapper.mapDn(group))
321         .name("ou")
322         .value("manager")
323         .build()));
324 
325     assertFalse(ldaptiveTemplate.compare(CompareRequest.builder()
326         .dn(groupMapper.mapDn(group))
327         .name("notexists")
328         .value("manager")
329         .build()));
330 
331     group.setCn("notexists");
332     CompareRequest cr = CompareRequest.builder()
333         .dn(groupMapper.mapDn(group))
334         .name("ou")
335         .value("developer")
336         .build();
337     assertThrows(ServiceException.class, () -> ldaptiveTemplate.compare(cr));
338   }
339 
340   /**
341    * Add and modify and delete person.
342    */
343   @Test
344   void addAndModifyAndDeletePerson() {
345     Person person = new Person();
346     person.setCn("A person");
347     person.setSn("Person");
348     person.setUid("person");
349 
350     String dn = personMapper.mapDn(person);
351     LdapEntry destination = new LdapEntry();
352     personMapper.map(person, destination);
353     destination.setDn(dn);
354     destination.addAttributes(new LdapAttribute(
355         "objectclass",
356         personMapper.getObjectClasses()));
357     ldaptiveTemplate.add(new AddRequest(dn, destination.getAttributes()));
358     assertTrue(ldaptiveTemplate.exists(person, personMapper));
359 
360     person.setSn("Surname");
361     ModifyRequest modifyRequest = personMapper
362         .mapAndComputeModifyRequest(person, destination)
363         .orElseThrow();
364     ldaptiveTemplate.modify(modifyRequest);
365 
366     person = ldaptiveTemplate.findOne(SearchRequest.objectScopeSearchRequest(dn), personMapper)
367         .orElseThrow(() -> ServiceException.notFound("Person", "person"));
368     assertEquals("Surname", person.getSn());
369 
370     person.setSn("");
371     modifyRequest = personMapper
372         .mapAndComputeModifyRequest(person, destination)
373         .orElseThrow();
374     ldaptiveTemplate.modify(modifyRequest);
375 
376     person = ldaptiveTemplate.findOne(SearchRequest.objectScopeSearchRequest(dn), personMapper)
377         .orElseThrow(() -> ServiceException.notFound("Person", "person"));
378     assertNull(person.getSn());
379 
380     ldaptiveTemplate.delete(new DeleteRequest(dn));
381     assertFalse(ldaptiveTemplate.exists(person, personMapper));
382   }
383 
384   /**
385    * Save and remove group.
386    */
387   @Test
388   void saveAndRemoveGroup() {
389     Group group = new Group();
390     group.setCn("party");
391     group.setOu("Party Guests");
392 
393     Set<String> members = new LinkedHashSet<>();
394     members.add("uid=anna,ou=people," + baseDn);
395     group.setMembers(members);
396 
397     group = ldaptiveTemplate.save(group, groupMapper);
398     assertNotNull(group);
399 
400     SearchRequest searchRequest = SearchRequest.builder()
401         .dn("ou=groups," + baseDn)
402         .filter(FilterTemplate.builder()
403             .filter("(&(objectclass=groupOfUniqueNames)(cn={0}))")
404             .parameters("party")
405             .build())
406         .scope(SearchScope.ONELEVEL)
407         .build();
408 
409     group = ldaptiveTemplate.findOne(searchRequest, groupMapper)
410         .orElseThrow(() -> ServiceException.notFound("Group", "party"));
411     assertEquals("party", group.getCn());
412     assertEquals("Party Guests", group.getOu());
413     assertTrue(group.getMembers().contains("uid=anna,ou=people," + baseDn));
414 
415     group.getMembers().add("uid=hans,ou=people," + baseDn);
416     group.getMembers().add("uid=gustav,ou=people," + baseDn);
417     group = ldaptiveTemplate.save(group, groupMapper);
418     assertNotNull(group);
419 
420     group = ldaptiveTemplate.findOne(searchRequest, groupMapper)
421         .orElseThrow(() -> ServiceException.notFound("Group", "party"));
422     assertEquals("party", group.getCn());
423     assertEquals("Party Guests", group.getOu());
424     assertTrue(group.getMembers().contains("uid=anna,ou=people," + baseDn));
425     assertTrue(group.getMembers().contains("uid=hans,ou=people," + baseDn));
426     assertTrue(group.getMembers().contains("uid=gustav,ou=people," + baseDn));
427 
428     group.getMembers().remove("uid=hans,ou=people," + baseDn);
429     group = ldaptiveTemplate.save(group, groupMapper);
430     assertNotNull(group);
431 
432     group = ldaptiveTemplate.findOne(searchRequest, groupMapper)
433         .orElseThrow(() -> ServiceException.notFound("Group", "party"));
434     assertEquals("party", group.getCn());
435     assertEquals("Party Guests", group.getOu());
436     assertTrue(group.getMembers().contains("uid=anna,ou=people," + baseDn));
437     assertTrue(group.getMembers().contains("uid=gustav,ou=people," + baseDn));
438     assertFalse(group.getMembers().contains("uid=hans,ou=people," + baseDn));
439 
440     ldaptiveTemplate.remove(group, groupMapper);
441     assertFalse(ldaptiveTemplate.exists(group, groupMapper));
442   }
443 
444   /**
445    * Save and delete persons.
446    */
447   @Test
448   void saveAndRemovePersons() {
449     Person p0 = new Person();
450     p0.setCn("Person Number 0");
451     p0.setSn("Person 0");
452     p0.setUid("person0");
453     Person p1 = new Person();
454     p1.setCn("Person Number 1");
455     p1.setSn("Person 1");
456     p1.setUid("person1");
457     Person p2 = new Person();
458     p2.setCn("Person Number 2");
459     p2.setSn("Person 2");
460     p2.setUid("person2");
461 
462     ldaptiveTemplate.saveAll(Arrays.asList(p0, p1, p2), personMapper)
463         .forEach(person -> log.info(String.format("New person: %s", person)));
464     assertTrue(ldaptiveTemplate.exists(p0, personMapper));
465     assertTrue(ldaptiveTemplate.exists(p1, personMapper));
466     assertTrue(ldaptiveTemplate.exists(p2, personMapper));
467 
468     ldaptiveTemplate.removeAll(Arrays.asList(p0, p1, p2), personMapper);
469     assertFalse(ldaptiveTemplate.exists(p0, personMapper));
470     assertFalse(ldaptiveTemplate.exists(p1, personMapper));
471     assertFalse(ldaptiveTemplate.exists(p2, personMapper));
472   }
473 
474   /**
475    * Modify dn.
476    */
477   @Test
478   void modifyDn() {
479     Person p0 = new Person();
480     p0.setCn("Gottfried");
481     p0.setSn("Benn");
482     p0.setUid("benn");
483 
484     p0 = ldaptiveTemplate.save(p0, personMapper);
485     assertNotNull(p0);
486 
487     ldaptiveTemplate.modifyDn(ModifyDnRequest.builder()
488         .oldDN(personMapper.mapDn(p0))
489         .newRDN("uid=gbn")
490         .delete(true)
491         .build());
492 
493     Optional<Person> pr = ldaptiveTemplate.findAll(
494             SearchRequest.builder()
495                 .dn("ou=people," + baseDn)
496                 .filter("(sn=Benn)")
497                 .build(),
498             personMapper)
499         .filter(p -> "gbn".equals(p.getUid()))
500         .findAny();
501     assertTrue(pr.isPresent());
502 
503     ldaptiveTemplate.delete(DeleteRequest.builder().dn(personMapper.mapDn(pr.get())).build());
504   }
505 
506   /**
507    * Test clone.
508    */
509   @Test
510   void testCopy() {
511     LdaptiveTemplate clone = ldaptiveTemplate.copy();
512     clone.setErrorHandler(new DefaultLdaptiveErrorHandler());
513     Group group = new Group();
514     group.setCn("developers");
515     assertTrue(clone.exists(group, groupMapper));
516     group.setCn("na");
517     assertFalse(clone.exists(group, groupMapper));
518   }
519 
520 }