diff --git a/docs/configuration.md b/docs/configuration.md index c90dc397..029cd033 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -92,28 +92,28 @@ Sugoi-api implements springdoc with full customization allowed Sugoi-api implements spring security with full customization allowed -| Properties | Description | Default value | example | -| ---------------------------------------------------------- | :---------: | ------------: | ------: | -| fr.insee.sugoi.cors.allowed-origins | | | | -| fr.insee.sugoi.cors.allowed-methods | | | | -| fr.insee.sugoi.security.basic-authentication-enabled | | | | -| fr.insee.sugoi.security.ldap-account-managment-enabled | | | | -| fr.insee.sugoi.security.ldap-account-managment-url | | | | -| fr.insee.sugoi.security.ldap-account-managment-user-base | | | | -| fr.insee.sugoi.security.ldap-account-managment-groupe-base | | | | -| fr.insee.sugoi.security.bearer-authentication-enabled | | | | -| spring.security.oauth2.resourceserver.jwt.jwk-set-uri | | | | -| fr.insee.sugoi.api.old.regexp.role.consultant | | | | -| fr.insee.sugoi.api.old.regexp.role.gestionnaire | | | | -| fr.insee.sugoi.api.old.regexp.role.admin | | | | -| fr.insee.sugoi.api.old.enable.preauthorize | | | | -| fr.insee.sugoi.api.regexp.role.reader | | | | -| fr.insee.sugoi.api.regexp.role.writer | | | | -| fr.insee.sugoi.api.regexp.role.admin | | | | -| fr.insee.sugoi.api.regexp.role.app.manager | | | | -| fr.insee.sugoi.api.regexp.role.password.manager | | | | -| fr.insee.sugoi.api.enable.preauthorize | | | | -| fr.insee.sugoi.security.default-roles-for-users | default role to add to each connected user | | | +| Properties | Description | Default value | example | +| ---------------------------------------------------------- |:----------------------------------------------------------------------------------------------------------------------:| ------------: | ------: | +| fr.insee.sugoi.cors.allowed-origins | | | | +| fr.insee.sugoi.cors.allowed-methods | | | | +| fr.insee.sugoi.security.basic-authentication-enabled | | | | +| fr.insee.sugoi.security.ldap-account-managment-enabled | | | | +| fr.insee.sugoi.security.ldap-account-managment-url | | | | +| fr.insee.sugoi.security.ldap-account-managment-user-base | | | | +| fr.insee.sugoi.security.ldap-account-managment-groupe-base | | | | +| fr.insee.sugoi.security.bearer-authentication-enabled | | | | +| spring.security.oauth2.resourceserver.jwt.jwk-set-uri | | | | +| fr.insee.sugoi.api.old.regexp.role.consultant | | | | +| fr.insee.sugoi.api.old.regexp.role.gestionnaire | | | | +| fr.insee.sugoi.api.old.regexp.role.admin | | | | +| fr.insee.sugoi.api.old.enable.preauthorize | | | | +| fr.insee.sugoi.api.regexp.role.reader | | | | +| fr.insee.sugoi.api.regexp.role.writer | | | | +| fr.insee.sugoi.api.regexp.role.admin | | | | +| fr.insee.sugoi.ldap.default.group_manager_source_pattern | Default pattern to use when searching manager group for application. Application name should be passed via {appliname} | | | +| fr.insee.sugoi.api.regexp.role.password.manager | | | | +| fr.insee.sugoi.api.enable.preauthorize | | | | +| fr.insee.sugoi.security.default-roles-for-users | default role to add to each connected user | | | #### Password configuration Passwords follows rules when there are passed by a user or randomly generated by Sugoi. A default for these rules which will apply to all realm that do not have its own configuration can be set by properties. For configuration at the realm level see [Realm configuration properties on password](realm-configuration.md#realm-configuration-properties-on-password). diff --git a/docs/realm-configuration.md b/docs/realm-configuration.md index 3b2b1ab8..73440450 100644 --- a/docs/realm-configuration.md +++ b/docs/realm-configuration.md @@ -91,6 +91,7 @@ A list of custom key/values can be added at the end. | groupsMaxOutputSize | 100 | yes | fr.insee.sugoi.groups.maxoutputsize | The maximum number of grouos outputs allowed | | applicationsMaxOutputSize | 100 | yes | fr.insee.sugoi.applications.maxoutputsize | The maximum number of applications outputs allowed | | organizationsMaxOutputSize | 100 | yes | fr.insee.sugoi.organizations.maxoutputsize | The maximum number of organizations outputs allowed | +| group_manager_source_pattern | "uid=ASI\_$(appliname),ou=Applications,o=insee,c=fr" | | the default can be set via the instance property : fr.insee.sugoi.ldap.default.group_manager_source_pattern | Describe where the group manager of the application {appliname} should be fetch. Users belonging to this group can create, delete, add or remove users from ${appliname}'s groups. | Realm configuration properties can be set as: @@ -104,17 +105,16 @@ A list of custom key/values can be added at the end. A UserStorage is a logical division of a Realm. These configuration should be set for each UserStorage contained in a Realm : -| Field name | Example | Optional | Default | Description | -| ---------------------------- | :--------------------------------------------------------------------------------: | -----------------------------------------------------------------------------------------: | -----------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | "myUserStorage" | no | | Name which identifies the userstorage in the realm | -| userSource | "ou=contacts,ou=clients_domaine1,o=insee,c=fr", "/realm1/users" | no | | The location of the users to read on the server. | -| organizationSource |  "ou=organisations,ou=clients_domaine1,o=insee,c=fr", "/realm1/organizations" | yes | | The location of the organization to read on the server. If organizationSource is not set, then organizations cannot be managed. | -| addressSource | "ou=adresses,ou=clients_domaine1,o=insee,c=fr" | Only used for ldap storage. Is needed with ldap storage for now but should become optional | | Addresses are stored as an independant resource in ldap storage. addressSource indicates the location of users and organizations address on the server. | -| properties | | might be needed depending on the type of store (see next sections) | | A list of other options which can be specific to the type of Store Provider. | -| readerType | "LdapReaderStore", "FileReaderStore" |  no | the default can be set via the instance property : fr.insee.sugoi.store.readerType  | Indicates wich type of store is used for reading. This attribute is read-only for now and should be set via default. | -| writeType | "JMSWriterStore", "LdapWriterStore", "FileWriterStore" |  no |  the default can be set via the instance property : fr.insee.sugoi.store.writerType  | Indicates wich type of store is used for writing. This attribute is read-only for now and should be set via default. | -| mappings | see [mappings section](#realm-and-userstorage-mappings-with-a-ldap-store-provider) | should be set when using a ldap store provider | see [mappings section](#realm-and-userstorage-mappings-with-a-ldap-store-provider) | Description of how to map Sugoi user and organization attributes with ldap attributes when using a ldap store provider | -| group_manager_source_pattern | "uid=ASI\_$(app),ou=Applications,o=insee,c=fr" | should be set when wanted to have a kind of group of group manager for app | | Description of where to put user who can manage apps groups | +| Field name | Example | Optional | Default | Description | +| ---------------------------- |:----------------------------------------------------------------------------------:|---------------------------------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------:| ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | "myUserStorage" | no | | Name which identifies the userstorage in the realm | +| userSource | "ou=contacts,ou=clients_domaine1,o=insee,c=fr", "/realm1/users" | no | | The location of the users to read on the server. | +| organizationSource |  "ou=organisations,ou=clients_domaine1,o=insee,c=fr", "/realm1/organizations" | yes | | The location of the organization to read on the server. If organizationSource is not set, then organizations cannot be managed. | +| addressSource | "ou=adresses,ou=clients_domaine1,o=insee,c=fr" | Only used for ldap storage. Is needed with ldap storage for now but should become optional | | Addresses are stored as an independant resource in ldap storage. addressSource indicates the location of users and organizations address on the server. | +| properties | | might be needed depending on the type of store (see next sections) | | A list of other options which can be specific to the type of Store Provider. | +| readerType | "LdapReaderStore", "FileReaderStore" |  no | the default can be set via the instance property : fr.insee.sugoi.store.readerType  | Indicates wich type of store is used for reading. This attribute is read-only for now and should be set via default. | +| writeType | "JMSWriterStore", "LdapWriterStore", "FileWriterStore" |  no |  the default can be set via the instance property : fr.insee.sugoi.store.writerType  | Indicates wich type of store is used for writing. This attribute is read-only for now and should be set via default. | +| mappings | see [mappings section](#realm-and-userstorage-mappings-with-a-ldap-store-provider) | should be set when using a ldap store provider | see [mappings section](#realm-and-userstorage-mappings-with-a-ldap-store-provider) | Description of how to map Sugoi user and organization attributes with ldap attributes when using a ldap store provider | ### Generic UserStorage properties diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/GenericLdapMapper.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/GenericLdapMapper.java index 723a75b3..16386467 100644 --- a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/GenericLdapMapper.java +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/mapper/GenericLdapMapper.java @@ -31,11 +31,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -193,25 +189,36 @@ private static Object transformLdapAttributeToSugoiAttribute( .map(attributeValue -> new User(LdapUtils.getNodeValueFromDN(attributeValue))) .collect(Collectors.toList()); case LIST_GROUP: - values = new ArrayList<>(); - attrs.stream().forEach(attribute -> values.addAll(Arrays.asList(attribute.getValues()))); - return values.stream() + Pattern patternSuffixGroup = + Pattern.compile( + config.get(LdapConfigKeys.GROUP_SOURCE_PATTERN).replace("{appliname}", "(.*)")); + Pattern patternGroupManager = + Pattern.compile( + config + .get(LdapConfigKeys.GROUP_MANAGER_SOURCE_PATTERN) + .replace("{appliname}", "(.*)")); + return attrs.stream() + .flatMap(attribute -> Arrays.stream(attribute.getValues())) .map( attributeValue -> { - Pattern pattern = - Pattern.compile( - config - .get(LdapConfigKeys.GROUP_SOURCE_PATTERN) - .replace("{appliname}", "(.*)")); - Matcher matcher = - pattern.matcher(attributeValue.substring(attributeValue.indexOf(",") + 1)); - if (matcher.matches()) { + // Match only suffix from application group + // TODO : Match real value of GROUP_SOURCE_PATTERN and not with substring(), + // without breaking other tests + String suffixGroup = attributeValue.substring(attributeValue.indexOf(",") + 1); + Matcher matcherSuffixGroup = patternSuffixGroup.matcher(suffixGroup); + // Match exact group pattern + Matcher matcherGroupManager = patternGroupManager.matcher(attributeValue); + if (matcherSuffixGroup.matches()) { + return new Group( + LdapUtils.getNodeValueFromDN(attributeValue), matcherSuffixGroup.group(1)); + } else if (matcherGroupManager.matches()) { return new Group( - LdapUtils.getNodeValueFromDN(attributeValue), matcher.group(1)); + LdapUtils.getNodeValueFromDN(attributeValue), matcherGroupManager.group(1)); } else { return null; } }) + .filter(Objects::nonNull) .collect(Collectors.toList()); case LIST_STRING: values = new ArrayList<>(); diff --git a/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromAttributesTest.java b/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromAttributesTest.java index 429be5f6..e5b7b5b9 100644 --- a/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromAttributesTest.java +++ b/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromAttributesTest.java @@ -28,13 +28,7 @@ import java.security.cert.X509Certificate; import java.text.ParseException; import java.time.Instant; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -53,6 +47,9 @@ public void setup() { config.put( LdapConfigKeys.GROUP_SOURCE_PATTERN, "ou={appliname}_Objets,ou={appliname},ou=Applications,o=insee,c=fr"); + config.put( + LdapConfigKeys.GROUP_MANAGER_SOURCE_PATTERN, + "cn=ASI_{appliname},ou={appliname},ou=Applications,o=insee,c=fr"); userLdapMapper = new UserLdapMapper(config, StoreMappingFixture.getUserStoreMappings()); } @@ -64,11 +61,8 @@ public void getSimpleUserFromAttributes() { Attribute firstNameAttribute = new Attribute("givenName", "Tata"); Attribute mailAttribute = new Attribute("mail", "toto@tata.insee.fr"); Attribute usernameAttribute = new Attribute("uid", "totoid"); - Collection attributes = new ArrayList<>(); - attributes.add(lastNameAttribute); - attributes.add(firstNameAttribute); - attributes.add(mailAttribute); - attributes.add(usernameAttribute); + Collection attributes = + List.of(lastNameAttribute, firstNameAttribute, mailAttribute, usernameAttribute); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat("Should have a username", mappedUser.getUsername(), is("totoid")); @@ -84,11 +78,8 @@ public void getUserAttributesFromAttributes() { Attribute personalTitleAttribute = new Attribute("personalTitle", "Camarade"); Attribute descriptionAttribute = new Attribute("description", "ma description"); Attribute telAttribute = new Attribute("telephoneNumber", "012345678"); - Collection attributes = new ArrayList<>(); - attributes.add(commonNameAttribute); - attributes.add(personalTitleAttribute); - attributes.add(descriptionAttribute); - attributes.add(telAttribute); + Collection attributes = + List.of(commonNameAttribute, personalTitleAttribute, descriptionAttribute, telAttribute); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat("Should hava a cn", mappedUser.getAttributes().get("common_name"), is("Toto Tata")); @@ -114,10 +105,8 @@ public void getUserHabilitationsFromAttributes() { Attribute habilitationWithoutPropAttribute = new Attribute("inseeGroupeDefaut", "role_application2"); Attribute malformedHabilitation = new Attribute("inseeGroupeDefaut", "toto"); - Collection attributes = new ArrayList<>(); - attributes.add(habilitationAttribute1); - attributes.add(habilitationWithoutPropAttribute); - attributes.add(malformedHabilitation); + Collection attributes = + List.of(habilitationAttribute1, habilitationWithoutPropAttribute, malformedHabilitation); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat( @@ -143,8 +132,7 @@ public void getUserOrganizationFromAttributes() { Attribute organizationAttribute = new Attribute( "inseeOrganisationDN", "uid=monOrga,ou=organisations,ou=clients_domaine1,o=insee,c=fr"); - Collection attributes = new ArrayList<>(); - attributes.add(organizationAttribute); + Collection attributes = List.of(organizationAttribute); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat( @@ -156,8 +144,7 @@ public void getUserAddressFromAttributes() { Attribute addressAttribute = new Attribute("inseeAdressePostaleDN", "l=generatedBefore,ou=address,o=insee,c=fr"); - Collection attributes = new ArrayList<>(); - attributes.add(addressAttribute); + Collection attributes = List.of(addressAttribute); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat("Should have address id", mappedUser.getAddress().getId(), is("generatedBefore")); @@ -171,21 +158,39 @@ public void getUserGroupFromAttributes() { "memberOf", "cn=admin,ou=monappli_Objets,ou=monappli,ou=Applications,o=insee,c=fr"); Attribute groupAttributes2 = new Attribute( - "memberOf", "cn=reader,ou=monappli_Objets,ou=monappli,ou=Applications,o=insee,c=fr"); - Collection attributes = new ArrayList<>(); - attributes.add(groupAttributes1); - attributes.add(groupAttributes2); + "memberOf", "cn=reader,ou=monappli2_Objets,ou=monappli2,ou=Applications,o=insee,c=fr"); + + Collection attributes = List.of(groupAttributes1, groupAttributes2); User mappedUser = userLdapMapper.mapFromAttributes(attributes); assertThat( - "Should have admin group", + "User should have admin group", mappedUser.getGroups().stream().anyMatch(group -> group.getName().equals("admin"))); assertThat( - "Admin group should have monappli app name", - mappedUser.getGroups().stream().anyMatch(group -> group.getAppName().equals("monappli"))); + "User should have at least a group of monappli2 app", + mappedUser.getGroups().stream().anyMatch(group -> group.getAppName().equals("monappli2"))); + } + + @Test + public void getUserManagerGroupFromAttribute() { + Attribute groupAttributesASI = + new Attribute("memberOf", "cn=ASI_toto,ou=toto,ou=Applications,o=insee,c=fr"); + + Attribute groupAttributes = + new Attribute( + "memberOf", "cn=admin,ou=monappli_Objets,ou=monappli,ou=Applications,o=insee,c=fr"); + Collection attributes = List.of(groupAttributesASI, groupAttributes); + User mappedUser = userLdapMapper.mapFromAttributes(attributes); + + assertThat( + "User should have ASI_toto group", + mappedUser.getGroups().stream().anyMatch(group -> group.getName().equals("ASI_toto"))); + assertThat( + "User should have admin name", + mappedUser.getGroups().stream().anyMatch(group -> group.getName().equals("admin"))); assertThat( - "Should have admin group", - mappedUser.getGroups().stream().anyMatch(group -> group.getName().equals("reader"))); + "User should have toto application name", + mappedUser.getGroups().stream().anyMatch(group -> group.getAppName().equals("toto"))); } @Test @@ -193,9 +198,7 @@ public void getInseeRolesApplicatifsFromAttributes() { Attribute inseeRoleAppAttribute1 = new Attribute("inseeRoleApplicatif", "toto"); Attribute inseeRoleAppAttribute2 = new Attribute("inseeRoleApplicatif", "tata"); - Collection attributes = new ArrayList<>(); - attributes.add(inseeRoleAppAttribute1); - attributes.add(inseeRoleAppAttribute2); + Collection attributes = List.of(inseeRoleAppAttribute1, inseeRoleAppAttribute2); User mappedUser = userLdapMapper.mapFromAttributes(attributes); @SuppressWarnings("unchecked") List inseeRoleApplicatifs = diff --git a/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromObjectTest.java b/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromObjectTest.java index 1501257d..6af90256 100644 --- a/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromObjectTest.java +++ b/sugoi-api-ldap-utils/src/test/java/fr/insee/sugoi/ldap/utils/mapper/UserLdapMapperFromObjectTest.java @@ -158,9 +158,7 @@ public void getUserHabilitationsAttributeFromJavaObject() { Habilitation habilitation1 = new Habilitation("property_role_application"); Habilitation habilitation2 = new Habilitation("property_role_application2"); - List habilitations = new ArrayList<>(); - habilitations.add(habilitation1); - habilitations.add(habilitation2); + List habilitations = List.of(habilitation1, habilitation2); user.setHabilitations(habilitations); List mappedAttributes = userLdapMapper.mapToAttributesForCreation(user);