diff --git a/docs/configuration.md b/docs/configuration.md index a8c34efb..a5643abe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -64,6 +64,7 @@ For each realm we have the possibility to configure a default reader and a defau | fr.insee.sugoi.ldap.default.application-mapping | List of mappings between sugoi application attributes and ldap attributes divided by semicolon, see [Realm configuration](realm-configuration.md) | name:ou,String,rw | | fr.insee.sugoi.id-create-length | Size of the ids randomly generated | 7 | | fr.insee.sugoi.reader-store-asynchronous | Is the reader store asynchronous, ie a difference can exist between what we read in readerstore and the realty. Can occur if the current service is connected by a broker to the real service. If true MAIL and ID unicity control are NOT performed | false | +| fr.insee.sugoi.fuzzy-search-allowed | If fuzzy search allowed, the user can ask to make an extensive request ignoring accents. | false | | fr.insee.sugoi.users.maxoutputsize | The default maximum number of user outputs allowed | 1000 | 100 | | fr.insee.sugoi.groups.maxoutputsize | The default maximum number of groups outputs allowed | 1000 | 100 | | fr.insee.sugoi.organizations.maxoutputsize | The default maximum number of organizations outputs allowed | 1000 | 100 | diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/UserService.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/UserService.java index c2f28552..8fe8eb65 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/UserService.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/UserService.java @@ -85,6 +85,8 @@ public interface UserService { * @param userProperties * @param pageable * @param typeRecherche + * @param fuzzySearchEnabled if true, the user common name should be searched without taking into + * account accents, spaces and other specials characters * @return a list of users */ PageResult findByProperties( @@ -92,7 +94,8 @@ PageResult findByProperties( String storageName, User userProperties, PageableResult pageable, - SearchType typeRecherche); + SearchType typeRecherche, + boolean fuzzySearchEnabled); /** * Allow to add only the app-managed attribute of an user, this attribute must follow the diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/impl/UserServiceImpl.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/impl/UserServiceImpl.java index 875503f3..bba7c221 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/impl/UserServiceImpl.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/service/impl/UserServiceImpl.java @@ -22,6 +22,7 @@ import fr.insee.sugoi.core.realm.RealmProvider; import fr.insee.sugoi.core.seealso.SeeAlsoService; import fr.insee.sugoi.core.service.UserService; +import fr.insee.sugoi.core.store.ReaderStore; import fr.insee.sugoi.core.store.StoreProvider; import fr.insee.sugoi.model.Realm; import fr.insee.sugoi.model.User; @@ -39,6 +40,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.passay.CharacterRule; import org.passay.PasswordGenerator; import org.slf4j.Logger; @@ -65,6 +67,8 @@ public class UserServiceImpl implements UserService { */ private boolean readerStoreAsynchronous = false; + private boolean fuzzySearchAllowed = false; + @Autowired private StoreProvider storeProvider; @Autowired private RealmProvider realmProvider; @@ -228,7 +232,8 @@ public PageResult findByProperties( String storage, User userProperties, PageableResult pageable, - SearchType typeRecherche) { + SearchType typeRecherche, + boolean fuzzySearchEnabled) { PageResult result = new PageResult<>(); Realm r = realmProvider.load(realm).orElseThrow(() -> new RealmNotFoundException(realm)); @@ -241,43 +246,34 @@ public PageResult findByProperties( .get(0))); result.setPageSize(pageable.getSize()); - if (storage != null) { - result = - storeProvider - .getReaderStore(realm, storage) - .searchUsers(userProperties, pageable, typeRecherche.name()); - result + List userStoragesToBrowse = + storage != null + ? List.of(storage) + : r.getUserStorages().stream().map(UserStorage::getName).collect(Collectors.toList()); + for (String usName : userStoragesToBrowse) { + ReaderStore readerStore = storeProvider.getReaderStore(realm, usName); + PageResult temResult = + fuzzySearchEnabled && fuzzySearchAllowed + ? readerStore.fuzzySearchUsers(userProperties, pageable, typeRecherche.name()) + : readerStore.searchUsers(userProperties, pageable, typeRecherche.name()); + temResult .getResults() .forEach( user -> { user.addMetadatas(EventKeysConfig.REALM, realm); - user.addMetadatas(EventKeysConfig.USERSTORAGE, storage); + user.addMetadatas(EventKeysConfig.USERSTORAGE, usName); }); - } else { - for (UserStorage us : r.getUserStorages()) { - PageResult temResult = - storeProvider - .getReaderStore(realm, us.getName()) - .searchUsers(userProperties, pageable, typeRecherche.name()); - temResult - .getResults() - .forEach( - user -> { - user.addMetadatas(EventKeysConfig.REALM, realm); - user.addMetadatas(EventKeysConfig.USERSTORAGE, us.getName()); - }); - result.getResults().addAll(temResult.getResults()); - result.setTotalElements( - temResult.getTotalElements() == -1 - ? temResult.getTotalElements() - : result.getTotalElements() + temResult.getTotalElements()); - result.setSearchToken(temResult.getSearchToken()); - result.setHasMoreResult(temResult.isHasMoreResult()); - if (result.getResults().size() >= result.getPageSize()) { - return result; - } - pageable.setSize(pageable.getSize() - result.getTotalElements()); + result.getResults().addAll(temResult.getResults()); + result.setTotalElements( + temResult.getTotalElements() == -1 + ? temResult.getTotalElements() + : result.getTotalElements() + temResult.getTotalElements()); + result.setSearchToken(temResult.getSearchToken()); + result.setHasMoreResult(temResult.isHasMoreResult()); + if (result.getResults().size() >= result.getPageSize()) { + return result; } + pageable.setSize(pageable.getSize() - result.getTotalElements()); } return result; diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/store/ReaderStore.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/store/ReaderStore.java index 554653a0..9e8bcec9 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/store/ReaderStore.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/store/ReaderStore.java @@ -59,6 +59,19 @@ public interface ReaderStore { public PageResult searchUsers( User userFilter, PageableResult pageable, String searchOperator); + /** + * Search users matching userFilter filled attributes. More results are returned than normal + * search since accents and other special characters are ignored. + * + * @param userFilter an incomplete user with attributes set to be matched with + * @param pageable properties for pageable request + * @param searchOperator 'OR' or 'AND' to determine if multiple attributes should match or only + * one + * @return a PageResult containing a list of matching users. + */ + public PageResult fuzzySearchUsers( + User userFilter, PageableResult pageable, String searchOperator); + /** * Retrieve the organization with the given id in the store. * diff --git a/sugoi-api-core/src/test/java/fr/insee/sugoi/core/service/UserServiceTest.java b/sugoi-api-core/src/test/java/fr/insee/sugoi/core/service/UserServiceTest.java index 62e24f51..0e0dcf52 100644 --- a/sugoi-api-core/src/test/java/fr/insee/sugoi/core/service/UserServiceTest.java +++ b/sugoi-api-core/src/test/java/fr/insee/sugoi/core/service/UserServiceTest.java @@ -190,7 +190,12 @@ public void findUsersShouldFailWhenRealmNotFound() { RealmNotFoundException.class, () -> userService.findByProperties( - "idonotexist", "us2", new User("Toto"), new PageableResult(), SearchType.AND)); + "idonotexist", + "us2", + new User("Toto"), + new PageableResult(), + SearchType.AND, + false)); } @Test @@ -309,7 +314,7 @@ public void getUsersWithPageableSizeShouldReturnEnoughUsers() { PageableResult pageable = new PageableResult(30000, 0, null); List results = userService - .findByProperties(realm.getName(), null, new User(), pageable, SearchType.AND) + .findByProperties(realm.getName(), null, new User(), pageable, SearchType.AND, false) .getResults(); assertThat("30000 users are retrieved", results.size(), is(30000)); assertThat( diff --git a/sugoi-api-file-store-provider/src/main/java/fr/insee/sugoi/store/file/FileReaderStore.java b/sugoi-api-file-store-provider/src/main/java/fr/insee/sugoi/store/file/FileReaderStore.java index 634f88a4..d8795a31 100644 --- a/sugoi-api-file-store-provider/src/main/java/fr/insee/sugoi/store/file/FileReaderStore.java +++ b/sugoi-api-file-store-provider/src/main/java/fr/insee/sugoi/store/file/FileReaderStore.java @@ -94,6 +94,12 @@ public PageResult searchUsers( return pageResult; } + @Override + public PageResult fuzzySearchUsers( + User userFilter, PageableResult pageable, String searchOperator) { + throw new NotImplementedException(); + } + @Override public Optional getOrganization(String id) { if (StringUtils.isNotBlank(config.get(GlobalKeysConfig.ORGANIZATION_SOURCE))) { diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java index f550ebb5..6258470a 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java @@ -40,9 +40,11 @@ import fr.insee.sugoi.model.paging.PageableResult; import fr.insee.sugoi.model.paging.SearchType; import fr.insee.sugoi.model.technics.StoreMapping; +import java.text.Normalizer; import java.util.*; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; +import org.springframework.util.Assert; public class LdapReaderStore extends LdapStore implements ReaderStore { @@ -130,6 +132,46 @@ public PageResult searchUsers( } } + @Override + public PageResult fuzzySearchUsers( + User userFilter, PageableResult pageable, String typeRecherche) { + String initialCommonName = (String) userFilter.getAttributes().get("common_name"); + if (initialCommonName == null) { + return searchUsers(userFilter, pageable, typeRecherche); + } else { + try { + userFilter + .getAttributes() + .put( + "common_name", + initialCommonName + .replaceAll( + "[ÀÁÂÃÄAÅÇCÈÉÊËEÌÍIÎÏÐÒÓÔOÕÖÙUÚÛÜÝYŸàáâãäåçèéêëìíîïðòóôõöùúûüýÿaeiouc \\-']", + "*") + .replaceAll("\\*+", "*")); + PageResult results = + searchOnLdap( + config.get(GlobalKeysConfig.USER_SOURCE), + SearchScope.SUBORDINATE_SUBTREE, + getFilterFromObject(userFilter, userLdapMapper, typeRecherche, false), + pageable, + userLdapMapper); + String normalizedCommonName = removeSpecialChars(initialCommonName); + List filteredUsers = + results.getResults().stream() + .filter( + u -> + removeSpecialChars((String) u.getAttributes().get("common_name")) + .equalsIgnoreCase(normalizedCommonName)) + .collect(Collectors.toList()); + results.setResults(filteredUsers); + return results; + } catch (LDAPException e) { + throw new StoreException("Fail to execute user search", e); + } + } + } + @Override public PageResult getUsersInGroup(String appName, String groupName) { PageResult page = new PageResult<>(); @@ -249,6 +291,11 @@ public PageResult searchApplications( } } + private Filter getFilterFromObject( + M object, LdapMapper mapper, String searchType) { + return getFilterFromObject(object, mapper, searchType, true); + } + /** * Create a filter from an object using a mapper class. Each set field of the object is * transformed to a filter. @@ -259,40 +306,68 @@ public PageResult searchApplications( * @return a filter corresponding to the properties of object */ private Filter getFilterFromObject( - M object, LdapMapper mapper, String searchType) { - if (searchType.equalsIgnoreCase("AND")) { + M object, LdapMapper mapper, String searchType, boolean encodeCommonNameWildcard) { + Assert.isTrue( + searchType.equals("AND") || searchType.equals("OR"), "Search type should be AND or OR."); + List attributes = mapper.createAttributesForFilter(object); + List attributeListFilter = getAttributesFilters(attributes, encodeCommonNameWildcard); + List objectClassListFilter = getObjectClassFilters(attributes); + if (!objectClassListFilter.isEmpty() && attributeListFilter.isEmpty()) { + return LdapFilter.and(objectClassListFilter); + } else if (objectClassListFilter.isEmpty() && !attributeListFilter.isEmpty()) { + return searchType.equals("AND") + ? LdapFilter.or(attributeListFilter) + : LdapFilter.and(attributeListFilter); + } else { return LdapFilter.and( - mapper.createAttributesForFilter(object).stream() - .filter(attribute -> !attribute.getValue().equals("")) - .map(attribute -> LdapFilter.createFilter(attribute.getName(), attribute.getValues())) - .collect(Collectors.toList())); - } else if (searchType.equalsIgnoreCase("OR")) { - List objectClassListFilter = - mapper.createAttributesForFilter(object).stream() - .filter(attribute -> attribute.getName().equals("objectClass")) - .map(attribute -> LdapFilter.createFilter(attribute.getName(), attribute.getValues())) - .collect(Collectors.toList()); + Arrays.asList( + LdapFilter.and(objectClassListFilter), + searchType.equals("AND") + ? LdapFilter.or(attributeListFilter) + : LdapFilter.and(attributeListFilter))); + } + } - List attributeListFilter = - mapper.createAttributesForFilter(object).stream() - .filter( - attribute -> - !attribute.getName().equals("objectClass") - && !attribute.getValue().equals("")) - .map(attribute -> LdapFilter.createFilter(attribute.getName(), attribute.getValues())) - .collect(Collectors.toList()); + private List getObjectClassFilters(List attributes) { + return attributes.stream() + .filter(attribute -> attribute.getName().equals("objectClass")) + .map(attribute -> LdapFilter.createFilter(attribute.getName(), attribute.getValues())) + .collect(Collectors.toList()); + } - if (!objectClassListFilter.isEmpty() && attributeListFilter.isEmpty()) { - return LdapFilter.and(objectClassListFilter); - } else if (objectClassListFilter.isEmpty() && !attributeListFilter.isEmpty()) { - return LdapFilter.or(attributeListFilter); + private List getAttributesFilters( + List attributes, boolean encodeCommonNameWildcard) { + List filters = + attributes.stream() + .filter( + attribute -> + !attribute.getName().equals("objectClass") + && !attribute.getValue().isEmpty() + && !attribute.getName().equals("cn")) + .map(attribute -> LdapFilter.createFilter(attribute.getName(), attribute.getValues())) + .collect(Collectors.toList()); + Optional commonNameAttribute = + attributes.stream().filter(attribute -> attribute.getName().equals("cn")).findFirst(); + if (commonNameAttribute.isPresent()) { + if (encodeCommonNameWildcard) { + filters.add( + LdapFilter.createFilter( + commonNameAttribute.get().getName(), commonNameAttribute.get().getValues())); } else { - return LdapFilter.and( - Arrays.asList( - LdapFilter.and(objectClassListFilter), LdapFilter.or(attributeListFilter))); + try { + filters.add( + Filter.create( + "cn=" + + Filter.encodeValue(commonNameAttribute.get().getValue()) + .replace("\\2a", "*"))); + } catch (LDAPException e) { + filters.add( + LdapFilter.createFilter( + commonNameAttribute.get().getName(), commonNameAttribute.get().getValues())); + } } } - throw new RuntimeException("Invalid searchType must be AND or OR"); + return filters; } private SearchResultEntry getEntryByDn(String dn) { @@ -420,4 +495,10 @@ private Optional getOrganization(String id, boolean isSubOrganizat } return Optional.ofNullable(org); } + + public String removeSpecialChars(String string) { + return Normalizer.normalize(string, Normalizer.Form.NFD) + .replaceAll("[-'\\s]+", "") + .replaceAll("\\p{InCombiningDiacriticalMarks}+", ""); + } } diff --git a/sugoi-api-ldap-store-provider/src/test/java/fr/insee/sugoi/ldap/LdapReaderStoreTest.java b/sugoi-api-ldap-store-provider/src/test/java/fr/insee/sugoi/ldap/LdapReaderStoreTest.java index 601e34ea..c208177d 100644 --- a/sugoi-api-ldap-store-provider/src/test/java/fr/insee/sugoi/ldap/LdapReaderStoreTest.java +++ b/sugoi-api-ldap-store-provider/src/test/java/fr/insee/sugoi/ldap/LdapReaderStoreTest.java @@ -187,6 +187,7 @@ public void testGetNonexistentUser() { @Test public void testSearchAllUsers() { PageableResult pageableResult = new PageableResult(); + pageableResult.setSize(50); List users = ldapReaderStore.searchUsers(new User(), pageableResult, "AND").getResults(); assertThat( "Should contain testo", @@ -209,6 +210,39 @@ public void testSearchUserWithMatchingMail() { assertThat("First element found should be testc", users.get(0).getUsername(), is("testc")); } + @Test + void testSearchUserWithCommonName() { + PageableResult pageableResult = new PageableResult(); + User testUser = new User(); + testUser.getAttributes().put("common_name", "Charlés d'Artagnan"); + List users = ldapReaderStore.searchUsers(testUser, pageableResult, "AND").getResults(); + assertThat("Should find one result", users.size(), is(1)); + assertThat("First element found should be testc", users.get(0).getUsername(), is("dartagnan1")); + } + + @Test + void testFuzzySearchUserWithCommonName() { + User testUser = new User(); + testUser.getAttributes().put("common_name", "Charlés d'Artagnan"); + List users = + ldapReaderStore.fuzzySearchUsers(testUser, new PageableResult(), "AND").getResults(); + assertThat("Should find two results", users.size(), is(2)); + assertThat( + "One result should be dartagnan1", + users.stream().anyMatch(u -> u.getUsername().equals("dartagnan1"))); + assertThat( + "The other dartagnan3", users.stream().anyMatch(u -> u.getUsername().equals("dartagnan3"))); + User testUser2 = new User(); + testUser2.getAttributes().put("common_name", "Charles-dArtag nan"); + assertThat( + "Two - are accepted", + ldapReaderStore + .fuzzySearchUsers(testUser2, new PageableResult(), "AND") + .getResults() + .stream() + .anyMatch(u -> u.getUsername().equals("dartagnan5"))); + } + @Test void testSearchUsersWithMatchingGroup() { User testUser = new User(); diff --git a/sugoi-api-ldap-store-provider/src/test/resources/ldap.ldif b/sugoi-api-ldap-store-provider/src/test/resources/ldap.ldif index f3b6b2ae..29bce606 100644 --- a/sugoi-api-ldap-store-provider/src/test/resources/ldap.ldif +++ b/sugoi-api-ldap-store-provider/src/test/resources/ldap.ldif @@ -279,6 +279,56 @@ cn: byebye memberOf: cn=Utilisateurs_Applitest,ou=Applitest_Objets,ou=Applitest,ou=Applications,o=insee,c=fr inseeAdressePostaleDN: l=supprime, ou=adresses,ou=clients_domaine1,o=insee,c=fr +dn: uid=dartagnan1,ou=contacts,ou=clients_domaine1,o=insee,c=fr +objectClass: top +objectClass: inseeCompte +objectClass: inseeContact +objectClass: inseeAttributsAuthentification +objectClass: inseeAttributsHabilitation +objectClass: inseeAttributsCommunication +uid: dartagnan1 +cn: Charlés d'Artagnan + +dn: uid=dartagnan2,ou=contacts,ou=clients_domaine1,o=insee,c=fr +objectClass: top +objectClass: inseeCompte +objectClass: inseeContact +objectClass: inseeAttributsAuthentification +objectClass: inseeAttributsHabilitation +objectClass: inseeAttributsCommunication +uid: dartagnan2 +cn: Chàrles-d-oArtagnan + +dn: uid=dartagnan3,ou=contacts,ou=clients_domaine1,o=insee,c=fr +objectClass: top +objectClass: inseeCompte +objectClass: inseeContact +objectClass: inseeAttributsAuthentification +objectClass: inseeAttributsHabilitation +objectClass: inseeAttributsCommunication +uid: dartagnan3 +cn: Charles d'Artàgnan + +dn: uid=dartagnan4,ou=contacts,ou=clients_domaine1,o=insee,c=fr +objectClass: top +objectClass: inseeCompte +objectClass: inseeContact +objectClass: inseeAttributsAuthentification +objectClass: inseeAttributsHabilitation +objectClass: inseeAttributsCommunication +uid: dartagnan4 +cn: Charleeees dArtagnan + +dn: uid=dartagnan5,ou=contacts,ou=clients_domaine1,o=insee,c=fr +objectClass: top +objectClass: inseeCompte +objectClass: inseeContact +objectClass: inseeAttributsAuthentification +objectClass: inseeAttributsHabilitation +objectClass: inseeAttributsCommunication +uid: dartagnan5 +cn: Charles--dArtag nan + dn: uid=asupprimer,ou=contacts,ou=clients_domaine1,o=insee,c=fr objectClass: top objectClass: inseeCompte diff --git a/sugoi-api-rest-old-services/src/main/java/fr/insee/sugoi/old/services/controller/ContactsDomaineController.java b/sugoi-api-rest-old-services/src/main/java/fr/insee/sugoi/old/services/controller/ContactsDomaineController.java index 3dec4b76..a32c83f3 100644 --- a/sugoi-api-rest-old-services/src/main/java/fr/insee/sugoi/old/services/controller/ContactsDomaineController.java +++ b/sugoi-api-rest-old-services/src/main/java/fr/insee/sugoi/old/services/controller/ContactsDomaineController.java @@ -293,7 +293,8 @@ public ResponseEntity getContacts( realmUserStorage.getUserStorage(), searchSugoiUser, pageable, - SearchType.AND); + SearchType.AND, + false); HttpHeaders headers = new HttpHeaders(); headers.add("X-Total-Size", String.valueOf(foundUsers.getTotalElements())); @@ -420,7 +421,8 @@ public ResponseEntity getContactsSize( realmUserStorage.getUserStorage(), new User(), pageable, - SearchType.AND); + SearchType.AND, + false); return ResponseEntity.status(HttpStatus.NO_CONTENT) .header("X-Total-Size", String.valueOf(foundUsers.getTotalElements())) .build(); diff --git a/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/ExportController.java b/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/ExportController.java index 152e4267..51347b19 100644 --- a/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/ExportController.java +++ b/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/ExportController.java @@ -266,7 +266,8 @@ private void getExportUsersWithStorages( while (true) { PageResult foundUsers = - userService.findByProperties(realm, storageName, searchUser, pageable, typeRecherche); + userService.findByProperties( + realm, storageName, searchUser, pageable, typeRecherche, false); for (User foundUser : foundUsers.getResults()) { List attributesToPrint = getCsvLineFromUser(headerMappings, foundUser); csvPrinter.printRecord(attributesToPrint); diff --git a/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/UserController.java b/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/UserController.java index 5f4610ce..0b1bf2d6 100644 --- a/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/UserController.java +++ b/sugoi-api-rest-services/src/main/java/fr/insee/sugoi/services/controller/UserController.java @@ -157,7 +157,10 @@ public ResponseEntity getUsers( List groups, @Parameter(description = "Filter on application. groupFilter parameter is required") @RequestParam(name = "applicationFilter", required = false) - String applicationFilter) { + String applicationFilter, + @Parameter(description = "Fuzzy search on common name enabled", required = false) + @RequestParam(name = "fuzzySearchEnabled", required = false, defaultValue = "false") + Boolean fuzzySearchEnabled) { // set the user which will serve as a model to retrieve the matching users User searchUser = new User(); @@ -188,7 +191,8 @@ public ResponseEntity getUsers( PageableResult pageable = new PageableResult(size, offset, searchCookie); PageResult foundUsers = - userService.findByProperties(realm, storage, searchUser, pageable, typeRecherche); + userService.findByProperties( + realm, storage, searchUser, pageable, typeRecherche, fuzzySearchEnabled); if (foundUsers.isHasMoreResult()) { URI location = ServletUriComponentsBuilder.fromCurrentRequest() @@ -266,7 +270,10 @@ public ResponseEntity getUsers( List groups, @Parameter(description = "Filter on application") @RequestParam(name = "applicationFilter", required = false) - String applicationFilter) { + String applicationFilter, + @Parameter(description = "Fuzzy search on common name enabled", required = false) + @RequestParam(name = "fuzzySearchEnabled", required = false, defaultValue = "false") + Boolean fuzzySearchEnabled) { return getUsers( realm, null, @@ -283,7 +290,8 @@ public ResponseEntity getUsers( typeRecherche, habilitations, groups, - applicationFilter); + applicationFilter, + fuzzySearchEnabled); } @PostMapping( diff --git a/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/ExportControllerTest.java b/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/ExportControllerTest.java index 224739bc..2c8c548d 100644 --- a/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/ExportControllerTest.java +++ b/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/ExportControllerTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import fr.insee.sugoi.commons.services.controller.technics.SugoiAdviceController; @@ -109,12 +110,14 @@ public void setup() { PageResult pageResultUserStorage1 = new PageResult<>(); pageResultUserStorage1.setResults(List.of(user, userAttribute)); - Mockito.when(userService.findByProperties(any(), eq("storage"), any(), any(), any())) + Mockito.when( + userService.findByProperties(any(), eq("storage"), any(), any(), any(), anyBoolean())) .thenReturn(pageResultUserStorage1); PageResult pageResultUserStorage2 = new PageResult<>(); pageResultUserStorage2.setResults(List.of(user1)); - Mockito.when(userService.findByProperties(any(), eq("storage2"), any(), any(), any())) + Mockito.when( + userService.findByProperties(any(), eq("storage2"), any(), any(), any(), anyBoolean())) .thenReturn(pageResultUserStorage2); UserStorage userStorage2 = new UserStorage(); diff --git a/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/UserControllerTest.java b/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/UserControllerTest.java index 22a8e430..fd8c0043 100644 --- a/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/UserControllerTest.java +++ b/sugoi-api-rest-services/src/test/java/fr/insee/sugoi/services/controller/UserControllerTest.java @@ -104,7 +104,8 @@ public void retrieveAllUsers() { Mockito.isNull(), Mockito.any(), Mockito.any(), - Mockito.any())) + Mockito.any(), + Mockito.anyBoolean())) .thenReturn(pageResult); RequestBuilder requestBuilder = @@ -277,7 +278,12 @@ public void getNextLocationInSearchResponse() { Mockito.when( userService.findByProperties( - Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) + Mockito.anyString(), + Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.anyBoolean())) .thenReturn(pageResult); RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/realms/domaine1/users?size=2")