diff --git a/docs/configuration.md b/docs/configuration.md index c071298e..7b921ee2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,34 +34,35 @@ Realm can be load from different sources. | fr.insee.sugoi.config.ldap.profils.pattern | Use only if config type is ldap. String pattern to find realms ('{realm}' is replaced with realm's name). cn={realm} wil search realm config for realm1 | | | | fr.insee.sugoi.ldap.default.vlv.enabled | enable vlv searched on ldap | false | | | fr.insee.sugoi.config.ldap.default.sortKey | attribute on which paging request will be ordered | | uid | +| fr.insee.sugoi.verify.unique.email | indicate if a check on user email must be done before each update/creation | | true | ### Reader writer configuration For each realm we have the possibility to configure a default reader and a default writer. For the moment it's possible to use ldap, file, and jms as writerStore and only ldap and file as reader. -| Properties | Description | Default value | example | -| ----------------------------------------------------- | :-------------------------------------------------------------------------------------: | ------------: | ----------------------------------------------: | -| fr.insee.sugoi.store.defaultReader | Can be LdapReaderStore, FileReaderStore | | LdapReaderStore | -| fr.insee.sugoi.store.defaultWriter | Can be LdapWriterStore, FileWriterStore, JmsWriterStore | | LdapWriterStore | -| fr.insee.sugoi.jms.broker.url | Use only if default writer or reader is JMS. | | ssl://broker.com:61617?verifyHostName=false | -| fr.insee.sugoi.jms.queue.requests.name | Use only if defaultWriter is JMS. | | queue.sugoi.developpement.requests | -| fr.insee.sugoi.jms.queue.response.name | Use only if defaultWriter is JMS. | | queue.sugoi.developpement.response | -| fr.insee.sugoi.jms.priority.queue.request.name | Name of the request queue to listen | | queue.sugoi.developpement.prioritaire.requests | -| fr.insee.sugoi.jms.priority.queue.response.name | Name of the response queue to listen | | queue.sugoi.developpement.prioritaire.responses | -| fr.insee.sugoi.jms.receiver.request.enabled | Enable to listen a request queue in a broker | | | -| fr.insee.sugoi.jms.receiver.response.enabled | Enable to listen a response queue in a broker | | | -| fr.insee.sugoi.ldap.default.ldap.pool | Use only if defaultWriter is ldap. Default pool size for each ldap connection | | 10 | -| fr.insee.sugoi.ldap.default.username | Use only if defaultWriter is ldap. Default username to establish connection with ldap | | cn=Directory Manager | -| fr.insee.sugoi.ldap.default.password | Use only if defaultWriter is ldap. Default password to establish connection with ldap | | admin | -| fr.insee.sugoi.ldap.default.port | Use only if defaultWriter is ldap. Default port to establish connection with ldap | | 10389 | -| fr.insee.sugoi.ldap.default.group_source_pattern | Use only if defaultWriter is ldap. Default pattern to follow to find group | | | -| fr.insee.sugoi.ldap.default.group_filter_pattern | Use only if defaultWriter is ldap. Default pattern to follow for naming groups | | | -| fr.insee.sugoi.default.app_managed_attribute_keys | a list of all attributes that a user can update directly | | -| fr.insee.sugoi.default.app_managed_attribute_patterns | Default pattern that each fr.insee.sugoi.default.app_managed_attribute_keys must follow | | -| fr.insee.sugoi.ldap.default.user-mapping | List of mappings between sugoi user attributes and ldap attributes divided by semicolon , see [Realm configuration](realm-configuration.md) | username:uid,String,rw;groups:memberOf,list_group,ro;habilitations:inseeGroupeDefaut,list_habilitation,rw | -| fr.insee.sugoi.ldap.default.organization-mapping | List of mappings between sugoi organization attributes and ldap attributes divided by semicolon, see [Realm configuration](realm-configuration.md) | identifiant:uid,String,rw;address:inseeAdressePostaleDN,address,rw;organization:inseeOrganisationDN,organization,rw | -| fr.insee.sugoi.ldap.default.group-mapping | List of mappings between sugoi group attributes and ldap attributes divided by semicolon, see [Realm configuration](realm-configuration.md) | name:cn,String,rw;description:description,String,rw;users:uniquemember,list_user,rw | -| 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 | +| Properties | Description | Default value | example | +| ----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------: | ----------------------------------------------: | +| fr.insee.sugoi.store.defaultReader | Can be LdapReaderStore, FileReaderStore | | LdapReaderStore | +| fr.insee.sugoi.store.defaultWriter | Can be LdapWriterStore, FileWriterStore, JmsWriterStore | | LdapWriterStore | +| fr.insee.sugoi.jms.broker.url | Use only if default writer or reader is JMS. | | ssl://broker.com:61617?verifyHostName=false | +| fr.insee.sugoi.jms.queue.requests.name | Use only if defaultWriter is JMS. | | queue.sugoi.developpement.requests | +| fr.insee.sugoi.jms.queue.response.name | Use only if defaultWriter is JMS. | | queue.sugoi.developpement.response | +| fr.insee.sugoi.jms.priority.queue.request.name | Name of the request queue to listen | | queue.sugoi.developpement.prioritaire.requests | +| fr.insee.sugoi.jms.priority.queue.response.name | Name of the response queue to listen | | queue.sugoi.developpement.prioritaire.responses | +| fr.insee.sugoi.jms.receiver.request.enabled | Enable to listen a request queue in a broker | | | +| fr.insee.sugoi.jms.receiver.response.enabled | Enable to listen a response queue in a broker | | | +| fr.insee.sugoi.ldap.default.ldap.pool | Use only if defaultWriter is ldap. Default pool size for each ldap connection | | 10 | +| fr.insee.sugoi.ldap.default.username | Use only if defaultWriter is ldap. Default username to establish connection with ldap | | cn=Directory Manager | +| fr.insee.sugoi.ldap.default.password | Use only if defaultWriter is ldap. Default password to establish connection with ldap | | admin | +| fr.insee.sugoi.ldap.default.port | Use only if defaultWriter is ldap. Default port to establish connection with ldap | | 10389 | +| fr.insee.sugoi.ldap.default.group_source_pattern | Use only if defaultWriter is ldap. Default pattern to follow to find group | | | +| fr.insee.sugoi.ldap.default.group_filter_pattern | Use only if defaultWriter is ldap. Default pattern to follow for naming groups | | | +| fr.insee.sugoi.default.app_managed_attribute_keys | a list of all attributes that a user can update directly | | +| fr.insee.sugoi.default.app_managed_attribute_patterns | Default pattern that each fr.insee.sugoi.default.app_managed_attribute_keys must follow | | +| fr.insee.sugoi.ldap.default.user-mapping | List of mappings between sugoi user attributes and ldap attributes divided by semicolon , see [Realm configuration](realm-configuration.md) | username:uid,String,rw;groups:memberOf,list_group,ro;habilitations:inseeGroupeDefaut,list_habilitation,rw | +| fr.insee.sugoi.ldap.default.organization-mapping | List of mappings between sugoi organization attributes and ldap attributes divided by semicolon, see [Realm configuration](realm-configuration.md) | identifiant:uid,String,rw;address:inseeAdressePostaleDN,address,rw;organization:inseeOrganisationDN,organization,rw | +| fr.insee.sugoi.ldap.default.group-mapping | List of mappings between sugoi group attributes and ldap attributes divided by semicolon, see [Realm configuration](realm-configuration.md) | name:cn,String,rw;description:description,String,rw;users:uniquemember,list_user,rw | +| 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 | ### SpringDoc configuration @@ -145,7 +146,8 @@ A metrics event module is provided to add metrics when events occured. This is d ``` fr.insee.sugoi.api.event.metrics.enabled=true ``` -All actuator endpoints are available to admin users. You can also enable a specific monitoring user with the properties : + +All actuator endpoints are available to admin users. You can also enable a specific monitoring user with the properties : ``` fr.insee.sugoi.security.monitor-user-enabled=true @@ -154,6 +156,7 @@ fr.insee.sugoi.security.monitor-user-password=monitor ``` This user only has rights on `/actuator` endpoints. + ### Other info configuration You can add all other spring properties for example : diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/configuration/GlobalKeysConfig.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/configuration/GlobalKeysConfig.java index 72122752..8a9bd2e2 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/configuration/GlobalKeysConfig.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/configuration/GlobalKeysConfig.java @@ -29,4 +29,5 @@ public class GlobalKeysConfig { public static final String APP_MANAGED_ATTRIBUTE_KEYS_LIST = "app-managed-attribute-keys-list"; public static final String APP_MANAGED_ATTRIBUTE_PATTERNS_LIST = "app-managed-attribute-patterns-list"; + public static final String VERIFY_MAIL_UNICITY = "verify_mail_unicity"; } diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/configuration/EventKeysConfig.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/configuration/EventKeysConfig.java index 05649470..4b9dc213 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/configuration/EventKeysConfig.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/configuration/EventKeysConfig.java @@ -39,6 +39,7 @@ public class EventKeysConfig extends GlobalKeysConfig { public static final String USER = "user"; public static final String USER_ID = "userId"; public static final String USER_NAME = "userName"; + public static final String USER_MAIL = "userMail"; public static final String USER_FILTER = "userFilter"; public static final String USER_PROPERTIES = "userProperties"; diff --git a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/model/SugoiEventTypeEnum.java b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/model/SugoiEventTypeEnum.java index a72bc4ea..1c785e5f 100644 --- a/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/model/SugoiEventTypeEnum.java +++ b/sugoi-api-core/src/main/java/fr/insee/sugoi/core/event/model/SugoiEventTypeEnum.java @@ -19,6 +19,7 @@ public enum SugoiEventTypeEnum { UPDATE_USER, FIND_USERS, FIND_USER_BY_ID, + FIND_USER_BY_MAIL, FIND_REALMS, FIND_REALM_BY_ID, CREATE_REALM, @@ -76,6 +77,7 @@ public enum SugoiEventTypeEnum { DELETE_USER_FROM_GROUP_ERROR, FIND_USERS_ERROR, FIND_USER_BY_ID_ERROR, + FIND_USER_BY_MAIL_ERROR, FIND_REALMS_ERROR, FIND_REALM_BY_ID_ERROR, FIND_ORGANIZATIONS_ERROR, 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 f45ed9e2..032f5698 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 @@ -68,6 +68,16 @@ public interface UserService { */ Optional findById(String realm, String storageName, String idep); + /** + * Find a user by its username in a realm + * + * @param realm + * @param storageName + * @param idep + * @return an optional of user + */ + Optional findByMail(String realm, String storageName, String mail); + /** * Find users by criterias in a realm * 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 bc46b3d4..aea71223 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 @@ -376,4 +376,79 @@ public ProviderResponse deleteAppManagedAttribute( throw e; } } + + @Override + public Optional findByMail(String realmName, String storageName, String mail) { + User user = null; + try { + if (mail != null) { + Realm realm = + realmProvider + .load(realmName) + .orElseThrow( + () -> new RealmNotFoundException("The realm " + realmName + " doesn't exist ")); + if (storageName != null) { + user = storeProvider.getReaderStore(realmName, storageName).getUserByMail(mail); + user.addMetadatas(GlobalKeysConfig.REALM, realmName.toLowerCase()); + user.addMetadatas(GlobalKeysConfig.USERSTORAGE, storageName.toLowerCase()); + } else { + for (UserStorage us : realm.getUserStorages()) { + try { + user = storeProvider.getReaderStore(realmName, us.getName()).getUserByMail(mail); + user.addMetadatas(GlobalKeysConfig.REALM, realmName); + user.addMetadatas(GlobalKeysConfig.USERSTORAGE, us.getName()); + break; + } catch (Exception e) { + logger.debug( + "Error when trying to find user with mail" + + mail + + " on realm " + + realmName + + " and userstorage " + + us + + " error " + + e.getMessage()); + } + } + } + if (seeAlsoService != null + && realm.getProperties().containsKey(GlobalKeysConfig.SEEALSO_ATTRIBUTES)) { + String[] seeAlsosAttributes = + realm + .getProperties() + .get(GlobalKeysConfig.SEEALSO_ATTRIBUTES) + .replace(" ", "") + .split(","); + List seeAlsos = new ArrayList<>(); + for (String seeAlsoAttribute : seeAlsosAttributes) { + Object seeAlsoAttributeValue = user.getAttributes().get(seeAlsoAttribute); + if (seeAlsoAttributeValue instanceof String) { + seeAlsos.add((String) seeAlsoAttributeValue); + } else if (seeAlsoAttributeValue instanceof List) { + ((List) seeAlsoAttributeValue) + .forEach(seeAlso -> seeAlsos.add(seeAlso.toString())); + } + } + for (String seeAlso : seeAlsos) { + seeAlsoService.decorateWithSeeAlso(user, seeAlso); + } + } + sugoiEventPublisher.publishCustomEvent( + realmName, + storageName, + SugoiEventTypeEnum.FIND_USER_BY_MAIL, + Map.ofEntries(Map.entry(EventKeysConfig.USER_MAIL, mail))); + } + return Optional.ofNullable(user); + } catch (Exception e) { + sugoiEventPublisher.publishCustomEvent( + realmName, + storageName, + SugoiEventTypeEnum.FIND_USER_BY_MAIL_ERROR, + Map.ofEntries( + Map.entry(EventKeysConfig.USER_MAIL, mail), + Map.entry(EventKeysConfig.ERROR, e.toString()))); + return Optional.ofNullable(user); + } + } } 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 8a54cac9..8d2b98c3 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 @@ -37,6 +37,15 @@ public interface ReaderStore { */ public User getUser(String id); + /** + * Only on realms where `unique_emails` is enabled, retrieve the user with the given mail in the + * store. + * + * @param mail + * @return the user with matching mail, null if no user matches + */ + public User getUserByMail(String id); + /** * Search users matching userFilter filled attributes. * diff --git a/sugoi-api-core/src/main/resources/application.properties b/sugoi-api-core/src/main/resources/application.properties index e63db716..0109e053 100644 --- a/sugoi-api-core/src/main/resources/application.properties +++ b/sugoi-api-core/src/main/resources/application.properties @@ -14,4 +14,6 @@ fr.insee.sugoi.password.create.length=12 fr.insee.sugoi.password.create.withDigits=true fr.insee.sugoi.password.create.withUpperCase=true fr.insee.sugoi.password.create.withLowerCase=true -fr.insee.sugoi.password.create.withSpecial=true \ No newline at end of file +fr.insee.sugoi.password.create.withSpecial=true + +fr.insee.sugoi.config.verify.unique.email=true \ No newline at end of file diff --git a/sugoi-api-distribution/sugoi-api-distribution-full-env/src/main/resources/ldap-data/init-ldap.ldif b/sugoi-api-distribution/sugoi-api-distribution-full-env/src/main/resources/ldap-data/init-ldap.ldif index d149eee2..7b93959a 100644 --- a/sugoi-api-distribution/sugoi-api-distribution-full-env/src/main/resources/ldap-data/init-ldap.ldif +++ b/sugoi-api-distribution/sugoi-api-distribution-full-env/src/main/resources/ldap-data/init-ldap.ldif @@ -123,6 +123,7 @@ inseepropriete: groupMapping$name:cn,String,rw inseepropriete: groupMapping$description:description,String,rw inseepropriete: groupMapping$users:uniquemember,list_user,rw inseepropriete: description$Le profil domaine2 +inseepropriete: enableMailUnicity$true dn: cn=monUserStorage,cn=Profil_domaine2_WebServiceLdap,cn=profil-contact-WebServicesLdap,ou=We diff --git a/sugoi-api-distribution/sugoi-api-distribution-jar/src/main/resources/application.properties b/sugoi-api-distribution/sugoi-api-distribution-jar/src/main/resources/application.properties index f31b99a0..0ae61e29 100644 --- a/sugoi-api-distribution/sugoi-api-distribution-jar/src/main/resources/application.properties +++ b/sugoi-api-distribution/sugoi-api-distribution-jar/src/main/resources/application.properties @@ -133,3 +133,4 @@ info.build.version=${project.version} fr.insee.sugoi.api.event.metrics.enabled=true +fr.insee.sugoi.config.verify.unique.email=true \ No newline at end of file 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 7c746a61..6b703c78 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 @@ -262,4 +262,20 @@ private ReturnClazz loadResourceContent( throw new RuntimeException("Failed to load resource", e); } } + + @Override + public User getUserByMail(String mail) { + logger.debug("Searching user with mail {}", mail); + User searchedUser = new User(); + searchedUser.setMail(mail); + PageResult users = searchUsers(searchedUser, null, null); + User user = null; + if (users.getResults().size() == 1) { + user = users.getResults().get(0); + } else if (users.getResults().size() > 1) { + throw new RuntimeException( + "multiple user found with this email where found cannot determine which one to choose"); + } + return user; + } } 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 a0f6c567..0ef67611 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 @@ -38,6 +38,7 @@ import fr.insee.sugoi.model.User; import fr.insee.sugoi.model.paging.PageResult; import fr.insee.sugoi.model.paging.PageableResult; +import fr.insee.sugoi.model.paging.SearchType; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -352,4 +353,31 @@ private Map getAddress(String addressId) { SearchResultEntry addressResult = getEntryByDn(getAddressDN(addressId)); return addressResult != null ? addressLdapMapper.mapFromSearchEntry(addressResult) : null; } + + @Override + public User getUserByMail(String mail) { + logger.debug("Searching user with mail {}", mail); + User searchedUser = new User(); + searchedUser.setMail(mail); + PageResult users = + searchUsers(searchedUser, new PageableResult(2, 0, null), SearchType.OR.name()); + User user = null; + if (users.getResults().size() == 1) { + user = users.getResults().get(0); + if (user.getAddress() != null && user.getAddress().containsKey("id")) { + Map address = getAddress(user.getAddress().get("id")); + if (address != null) { + address.put("id", user.getAddress().get("id")); + user.setAddress(address); + } + } + if (user.getOrganization() != null) { + user.setOrganization(getOrganization(user.getOrganization().getIdentifiant())); + } + } else if (users.getResults().size() > 1) { + throw new RuntimeException( + "multiple user found with this email where found cannot determine which one to choose"); + } + return user; + } } diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java index 8ce0adee..7902aae8 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java @@ -13,6 +13,7 @@ */ package fr.insee.sugoi.store.ldap; +import fr.insee.sugoi.core.configuration.GlobalKeysConfig; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.model.Realm; import fr.insee.sugoi.model.UserStorage; @@ -104,6 +105,11 @@ public Map generateConfig(Realm realm, UserStorage userStorage) config.put(LdapConfigKeys.USERNAME, defaultUsername); config.put(LdapConfigKeys.PASSWORD, defaultPassword); config.put(LdapConfigKeys.POOL_SIZE, defaultPoolSize); + config.put( + LdapConfigKeys.UNIQUE_EMAILS, + realm.getProperties().get(GlobalKeysConfig.VERIFY_MAIL_UNICITY) != null + ? realm.getProperties().get(GlobalKeysConfig.VERIFY_MAIL_UNICITY) + : "false"); config.put(LdapConfigKeys.USER_SOURCE, userStorage.getUserSource()); config.put(LdapConfigKeys.APP_SOURCE, realm.getAppSource()); config.put(LdapConfigKeys.ORGANIZATION_SOURCE, userStorage.getOrganizationSource()); diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java index 5509b9ca..ac697e96 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java @@ -128,6 +128,11 @@ public ProviderResponse deleteUser(String id, ProviderRequest providerRequest) { @Override public ProviderResponse createUser(User user, ProviderRequest providerRequest) { if (ldapReaderStore.getUser(user.getUsername()) == null) { + if (Boolean.parseBoolean(config.get(LdapConfigKeys.UNIQUE_EMAILS)) + && user.getMail() != null + && ldapReaderStore.getUserByMail(user.getMail()) != null) { + throw new UserAlreadyExistException("An user with this email already exist"); + } try { if (user.getAddress() != null && user.getAddress().size() > 0) { UUID addressUuid = createAddress(user.getAddress()); @@ -158,6 +163,13 @@ public ProviderResponse createUser(User user, ProviderRequest providerRequest) { @Override public ProviderResponse updateUser(User updatedUser, ProviderRequest providerRequest) { if (ldapReaderStore.getUser(updatedUser.getUsername()) != null) { + User temp = ldapReaderStore.getUserByMail(updatedUser.getMail()); + if (Boolean.parseBoolean(config.get(LdapConfigKeys.UNIQUE_EMAILS)) + && updatedUser.getMail() != null + && temp != null + && temp.getUsername() != updatedUser.getUsername()) { + throw new UserAlreadyExistException("An user with this email already exist"); + } try { if (updatedUser != null) { User currentUser = ldapReaderStore.getUser(updatedUser.getUsername()); diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java index f7d247a8..78000e94 100644 --- a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java @@ -35,4 +35,5 @@ public class LdapConfigKeys extends GlobalKeysConfig { public static final String APPLICATION_OBJECT_CLASSES = "application_object_classes"; public static final String ADDRESS_OBJECT_CLASSES = "address_object_classes"; public static final String USERSTORAGE_NAME = "userstorage_name"; + public static final String UNIQUE_EMAILS = "unique_emails"; } diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/RealmLdapMapper.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/RealmLdapMapper.java index 46e7cedb..715e2854 100644 --- a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/RealmLdapMapper.java +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/RealmLdapMapper.java @@ -40,6 +40,8 @@ public static Realm mapFromSearchEntry(SearchResultEntry searchResultEntry) { if (property.length == 2) { if (property[0].equalsIgnoreCase("ldapUrl")) { realm.setUrl(property[1]); + } else if (property[0].equalsIgnoreCase("enableMailUnicity")) { + realm.addProperty(GlobalKeysConfig.VERIFY_MAIL_UNICITY, property[1]); } else if (property[0].equalsIgnoreCase("branchesApplicativesPossibles")) { realm.setAppSource(property[1]); } else if (property[0].equalsIgnoreCase("seealso_attributes")) { 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 a0d476ed..4b4eb883 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 @@ -14,11 +14,13 @@ package fr.insee.sugoi.services.controller; import fr.insee.sugoi.core.configuration.GlobalKeysConfig; +import fr.insee.sugoi.core.exceptions.RealmNotFoundException; import fr.insee.sugoi.core.exceptions.UserNotFoundException; import fr.insee.sugoi.core.model.ProviderRequest; import fr.insee.sugoi.core.model.ProviderResponse; import fr.insee.sugoi.core.model.ProviderResponse.ProviderResponseStatus; import fr.insee.sugoi.core.model.SugoiUser; +import fr.insee.sugoi.core.realm.RealmProvider; import fr.insee.sugoi.core.service.UserService; import fr.insee.sugoi.model.Habilitation; import fr.insee.sugoi.model.Organization; @@ -68,6 +70,8 @@ public class UserController { @Autowired private UserService userService; + @Autowired private RealmProvider realmService; + @GetMapping( path = {"/realms/{realm}/storages/{storage}/users"}, produces = {MediaType.APPLICATION_JSON_VALUE}) @@ -632,4 +636,79 @@ public ResponseEntity getUserByUsername( String id) { return getUserByUsername(realm, null, id); } + + @GetMapping( + path = {"/realms/{realm}/storages/{storage}/users/mail/{mail}"}, + produces = {MediaType.APPLICATION_JSON_VALUE}) + @Operation(summary = "Get user by mail") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "User found according to parameters", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = User.class)) + }) + }) + @PreAuthorize("@NewAuthorizeMethodDecider.isReader(#realm,#storage)") + public ResponseEntity getUserByMail( + @Parameter( + description = "Name of the realm where the operation will be made", + required = true) + @PathVariable("realm") + String realm, + @Parameter( + description = "Name of the userStorage where the operation will be made", + required = false) + @PathVariable(name = "storage", required = false) + String storage, + @Parameter(description = "User's mail to search", required = true) @PathVariable("mail") + String mail) { + if (Boolean.parseBoolean( + realmService + .load(realm) + .orElseThrow(() -> new RealmNotFoundException("Realm " + realm + " doesn't exist")) + .getProperties() + .get(GlobalKeysConfig.VERIFY_MAIL_UNICITY))) { + + User user = + userService + .findByMail(realm, storage, mail) + .orElseThrow( + () -> + new UserNotFoundException( + "Cannot find user with mail " + mail + " in realm " + realm)); + return ResponseEntity.status(HttpStatus.OK).body(user); + } + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); + } + + @GetMapping( + path = {"/realms/{realm}/users/mail/{mail}"}, + produces = {MediaType.APPLICATION_JSON_VALUE}) + @Operation(summary = "Get user by mail") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "User found according to parameters", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = User.class)) + }) + }) + @PreAuthorize("@NewAuthorizeMethodDecider.isReader(#realm,#storage)") + public ResponseEntity getUserByMail( + @Parameter( + description = "Name of the realm where the operation will be made", + required = true) + @PathVariable("realm") + String realm, + @Parameter(description = "User's mail to search", required = true) @PathVariable("mail") + String mail) { + return getUserByMail(realm, null, mail); + } } 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 0e77dd61..b5d3d8ee 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 @@ -26,6 +26,7 @@ import fr.insee.sugoi.core.exceptions.UserNotFoundException; import fr.insee.sugoi.core.model.ProviderResponse; import fr.insee.sugoi.core.model.ProviderResponse.ProviderResponseStatus; +import fr.insee.sugoi.core.realm.RealmProvider; import fr.insee.sugoi.core.service.UserService; import fr.insee.sugoi.model.User; import fr.insee.sugoi.model.paging.PageResult; @@ -60,6 +61,8 @@ public class UserControllerTest { @MockBean private UserService userService; + @MockBean private RealmProvider realmService; + ObjectMapper objectMapper = new ObjectMapper(); User user1, user2, user1Updated; PageResult pageResult; diff --git a/sugoi-api-test/src/main/resources/ldap-data/init-ldap.ldif b/sugoi-api-test/src/main/resources/ldap-data/init-ldap.ldif index 4216b9fd..4049740d 100644 --- a/sugoi-api-test/src/main/resources/ldap-data/init-ldap.ldif +++ b/sugoi-api-test/src/main/resources/ldap-data/init-ldap.ldif @@ -256,6 +256,7 @@ inseepropriete: userMapping$attributes.properties:inseePropriete,list_string,rw inseepropriete: userMapping$metadatas.modifyTimestamp:modifyTimestamp,string,ro inseepropriete: userMapping$attributes.seeAlsos:seeAlso,list_string,ro,singl description: domaine1 +inseepropriete: enableMailUnicity$true dn: cn=Utilisateurs_contacts_domaine1_WebServicesLdap,ou=WebServicesLdap_Obje ts,ou=WebServicesLdap,ou=Applications,o=insee,c=fr diff --git a/sugoi-api-test/src/test/resources/scenario/user_scenario_sync.feature b/sugoi-api-test/src/test/resources/scenario/user_scenario_sync.feature index 653a3116..0c13aa09 100644 --- a/sugoi-api-test/src/test/resources/scenario/user_scenario_sync.feature +++ b/sugoi-api-test/src/test/resources/scenario/user_scenario_sync.feature @@ -32,13 +32,25 @@ Feature: User scenario When the client perform POST request with body on url /realms/domaine1/storages/Profil_domaine1_WebServiceLdap/users body: """ { - "username": "abcd" + "username": "abcd", + "mail": "test@insee.fr" } """ And show body received Then the client receives status code 201 Then the client expect to receive an user + Scenario: Post user with email already exit + When the client perform POST request with body on url /realms/domaine1/storages/Profil_domaine1_WebServiceLdap/users body: + """ + { + "username": "abcd2", + "mail": "test@insee.fr" + } + """ + And show body received + Then the client receives status code 409 + Scenario: Post user already exist When the client perform POST request with body on url /realms/domaine1/storages/Profil_domaine1_WebServiceLdap/users body: """ @@ -144,3 +156,8 @@ Feature: User scenario When the client perform DELETE request on url /realms/domaine1/storages/Profil_domaine1_WebServiceLdap/users/abcd And show body received Then the client receives status code 404 + + Scenario: Get user by mail on realm without mail unicity + When the client perform GET request on url /realms/domaine2/users/mail/invalid@mail.fr + And show body received + Then the client receives status code 501 \ No newline at end of file