From be22f689e688485880f426057797238906dee848 Mon Sep 17 00:00:00 2001 From: Julien CORNILLE <julien.cornille@xelians.fr> Date: Thu, 22 Apr 2021 09:39:53 +0200 Subject: [PATCH] [US RABB-1166] Fix --- .../iam/common/dto}/ProvidedUserDto.java | 3 +- api/api-iam/iam-internal/README.md | 49 ++++++ .../config/iam-internal-application-dev.yml | 16 +- .../cas/service/CasInternalService.java | 54 +++++-- .../server/config/ApiIamServerConfig.java | 6 +- .../client/ProvisioningWebClient.java | 93 ++++++++++++ .../IdPProvisioningClientConfiguration.java | 5 +- .../ProvisioningClientConfiguration.java | 2 +- .../service/ProvisioningInternalService.java | 78 +++++----- .../server/rest/CasInternalController.java | 4 +- .../server/user/dao/UserRepository.java | 2 + .../cas/service/CasInternalServiceTest.java | 57 ++++--- .../client/ProvisioningWebClientTest.java | 119 +++++++++++++++ .../ProvisioningInternalServiceTest.java | 143 ++++++++++++++++++ 14 files changed, 540 insertions(+), 91 deletions(-) rename {commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/domain => api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto}/ProvidedUserDto.java (90%) create mode 100644 api/api-iam/iam-internal/README.md create mode 100644 api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClient.java rename {commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration => api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config}/IdPProvisioningClientConfiguration.java (92%) rename {commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration => api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config}/ProvisioningClientConfiguration.java (97%) create mode 100644 api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClientTest.java create mode 100644 api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalServiceTest.java diff --git a/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/domain/ProvidedUserDto.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/ProvidedUserDto.java similarity index 90% rename from commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/domain/ProvidedUserDto.java rename to api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/ProvidedUserDto.java index a0a1e48ff..74ebb87b4 100644 --- a/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/domain/ProvidedUserDto.java +++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/ProvidedUserDto.java @@ -1,4 +1,4 @@ -package fr.gouv.vitamui.commons.api.domain; +package fr.gouv.vitamui.iam.common.dto; import org.hibernate.validator.constraints.Length; @@ -8,6 +8,7 @@ import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import fr.gouv.vitamui.commons.api.deserializer.ToLowerCaseConverter; +import fr.gouv.vitamui.commons.api.domain.AddressDto; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; diff --git a/api/api-iam/iam-internal/README.md b/api/api-iam/iam-internal/README.md new file mode 100644 index 000000000..001b908c8 --- /dev/null +++ b/api/api-iam/iam-internal/README.md @@ -0,0 +1,49 @@ +# Presentation + +This module is a set of REST/JSON web services to perform CRUD operations on the business models: + +- customers +- tenants +- identity providers +- profile groups +- profiles +- users. + + +# Run the web services + +```shell +mvn spring-boot:run +``` + +# Provisioning + +The users can be provided by a tier service + +To enable this function, you must configure an external identity provider with the property autoprovisioning set to true. + +At the module level, you must add properties to do the mapping between the identity provider and the webservice to call + +Example : **application.yml** + +``` + provisioning-client: + identity-providers: + - idp-identifier: system_idp + uri: https://localhost:8090/provisioning/v1/users + client: + secure: true + ssl-configuration: + keystore: + key-path: src/main/config/keystore_provisioning-users.jks + key-password: changeme + type: JKS + truststore: + key-path: src/main/config/truststore_server.jks + key-password: changeme + type: JKS + hostname-verification: false +``` + +Please note that the configuration take a list in input + diff --git a/api/api-iam/iam-internal/src/main/config/iam-internal-application-dev.yml b/api/api-iam/iam-internal/src/main/config/iam-internal-application-dev.yml index 7b44aa02b..e459151a4 100644 --- a/api/api-iam/iam-internal/src/main/config/iam-internal-application-dev.yml +++ b/api/api-iam/iam-internal/src/main/config/iam-internal-application-dev.yml @@ -84,33 +84,31 @@ logging: provisioning-client: identity-providers: - - idp-identifier: 1 + - idp-identifier: system_idp + uri: https://localhost:8090/provisioning/v1/users client: - server-host: ${vitamui-server-host}.teamdlab.com - server-port: 6201 secure: true ssl-configuration: keystore: - key-path: ../../../dev-deployment/environment/keystores/server/localhost/keystore_archive-internal.jks + key-path: src/main/config/keystore_provisioning-users.jks key-password: changeme type: JKS truststore: - key-path: ../../../dev-deployment/environment/keystores/server/truststore_server.jks + key-path: src/main/config/truststore_server.jks key-password: changeme type: JKS hostname-verification: false - idp-identifier: 2 client: - server-host: ${vitamui-server-host}.teamdlab.com - server-port: 6201 + uri: https://localhost:8090/provisioning/v1/users secure: true ssl-configuration: keystore: - key-path: ../../../dev-deployment/environment/keystores/server/localhost/keystore_archive-internal.jks + key-path: src/main/config/keystore_provisioning-users.jks key-password: changeme type: JKS truststore: - key-path: ../../../dev-deployment/environment/keystores/server/truststore_server.jks + key-path: src/main/config/truststore_server.jks key-password: changeme type: JKS hostname-verification: false diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java index 836c01b23..cfcbfd41e 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java @@ -66,7 +66,7 @@ import javax.validation.constraints.NotNull; import fr.gouv.vitamui.commons.api.CommonConstants; import fr.gouv.vitamui.commons.api.domain.CriterionOperator; import fr.gouv.vitamui.commons.api.domain.GroupDto; -import fr.gouv.vitamui.commons.api.domain.ProvidedUserDto; +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; import fr.gouv.vitamui.commons.api.domain.QueryDto; import fr.gouv.vitamui.commons.api.domain.UserDto; import fr.gouv.vitamui.commons.api.enums.UserStatusEnum; @@ -293,25 +293,53 @@ public class CasInternalService { } } + /** + * Method to retrieve the user informations + * @param email email of the user + * @param idp can be null + * @param userIdentifier can be null + * @param optEmbedded + * @return + */ @Transactional - public UserDto getUser(final String email, final String idp, final Optional<String> userIdentifier, final Optional<String> optEmbedded) { + public UserDto getUser(final String email, final String idp, final String userIdentifier, final String optEmbedded) { + // if the user depends on an external idp + if(StringUtils.isNotBlank(idp)) { + this.provisionUser(email, idp, userIdentifier); + } + + return getUserByEmail(email, Optional.ofNullable(optEmbedded)); + } + + /** + * Method to perform auto provisioning + * @param email + * @param idp + * @param userIdentifier + */ + public void provisionUser(final String email, final String idp, final String userIdentifier) { final IdentityProviderDto identityProvider = identityProviderInternalService.getOne(idp); - try { + + // Do nothing is autoProvisioning is disabled + if(!identityProvider.isAutoProvisioningEnabled()) { + return; + } + + final boolean userExist = userRepository.existsByEmail(email); + // Try to update user + if(userExist) { final UserDto user = internalUserService.findUserByEmail(email); - if (identityProvider.isAutoProvisioningEnabled() && user.isAutoProvisioningEnabled()) { - // TODO : quelle unité envoyer ? - updateUser(user, provisioningInternalService.getUserInformation(email, idp, Optional.of(user.getGroupId()), Optional.empty(), userIdentifier)); - } - } catch (NotFoundException e) { - if (!identityProvider.isAutoProvisioningEnabled()) { - throw e; + if(user.isAutoProvisioningEnabled()) { + updateUser(user, provisioningInternalService.getUserInformation(idp, email, user.getGroupId(), null, userIdentifier)); } - createNewUser(provisioningInternalService.getUserInformation(email, idp, Optional.empty(), Optional.empty(), userIdentifier)); } - - return getUserByEmail(email, optEmbedded); + // Try to create a new user + else { + createNewUser(provisioningInternalService.getUserInformation(idp, email, null, null, null)); + } } + private void createNewUser(final ProvidedUserDto providedUserInfo) { final UserDto user = new UserDto(); user.setType(UserTypeEnum.NOMINATIVE); diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/config/ApiIamServerConfig.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/config/ApiIamServerConfig.java index bf07e6d5b..2f166e6ef 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/config/ApiIamServerConfig.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/config/ApiIamServerConfig.java @@ -42,7 +42,7 @@ import fr.gouv.vitamui.commons.mongo.config.MongoConfig; import fr.gouv.vitamui.commons.mongo.dao.CustomSequenceRepository; import fr.gouv.vitamui.commons.rest.RestExceptionHandler; import fr.gouv.vitamui.commons.rest.client.BaseRestClientFactory; -import fr.gouv.vitamui.commons.rest.client.configuration.ProvisioningClientConfiguration; +import fr.gouv.vitamui.iam.internal.server.provisioning.config.ProvisioningClientConfiguration; import fr.gouv.vitamui.commons.rest.client.configuration.RestClientConfiguration; import fr.gouv.vitamui.commons.rest.configuration.SwaggerConfiguration; import fr.gouv.vitamui.commons.vitam.api.access.LogbookService; @@ -334,8 +334,8 @@ public class ApiIamServerConfig extends AbstractContextConfiguration { } @Bean - public ProvisioningInternalService provisioningService(final WebClient.Builder webClientBuilder, final ProvisioningClientConfiguration provisioningClientConfiguration) { - return new ProvisioningInternalService(webClientBuilder, provisioningClientConfiguration); + public ProvisioningInternalService provisioningService(final WebClient.Builder webClientBuilder, final ProvisioningClientConfiguration provisioningClientConfiguration, final InternalSecurityService internalSecurityService) { + return new ProvisioningInternalService(webClientBuilder, provisioningClientConfiguration, internalSecurityService); } @Bean diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClient.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClient.java new file mode 100644 index 000000000..15d26016e --- /dev/null +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClient.java @@ -0,0 +1,93 @@ +/** + * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020) + * and the signatories of the "VITAM - Accord du Contributeur" agreement. + * + * contact@programmevitam.fr + * + * This software is a computer program whose purpose is to implement + * implement a digital archiving front-office system for the secure and + * efficient high volumetry VITAM solution. + * + * This software is governed by the CeCILL-C license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-C + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ +package fr.gouv.vitamui.iam.internal.server.provisioning.client; + +import java.net.URI; +import java.util.Optional; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.client.utils.URIBuilder; +import org.springframework.web.reactive.function.client.WebClient; + +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; +import fr.gouv.vitamui.commons.api.logger.VitamUILogger; +import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; +import fr.gouv.vitamui.commons.rest.client.BaseCrudWebClient; +import fr.gouv.vitamui.commons.rest.client.BaseWebClient; +import fr.gouv.vitamui.commons.rest.client.InternalHttpContext; + +/** + * External WebClient for Customer operations. + * + * + */ +public class ProvisioningWebClient extends BaseWebClient<InternalHttpContext> { + + private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(ProvisioningWebClient.class); + + public ProvisioningWebClient(final WebClient webClient, final String baseUrl) { + super(webClient, baseUrl); + } + + public ProvidedUserDto getProvidedUser(final InternalHttpContext context, final String email, final String groupId, final String unit, final String technicalUserId) { + + final URIBuilder builder = getUriBuilderFromUrl(); + + if (StringUtils.isNotBlank(email)) { + builder.addParameter("email", email); + } + if (StringUtils.isNotBlank(groupId)) { + builder.addParameter("groupId", groupId); + } + if (StringUtils.isNotBlank(unit)) { + builder.addParameter("unit", unit); + } + if (StringUtils.isNotBlank(technicalUserId)) { + builder.addParameter("technicalUserId", technicalUserId); + } + + return webClient.get().uri(buildUriBuilder(builder)).headers(headersConsumer -> headersConsumer.addAll(buildHeaders(context))).retrieve().onStatus(status -> !status.is2xxSuccessful(), BaseCrudWebClient::createResponseException) + .bodyToMono(ProvidedUserDto.class).block(); + } + + //PATH URL is given by configuration + @Override + public String getPathUrl() { + return StringUtils.EMPTY; + } + +} diff --git a/commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/IdPProvisioningClientConfiguration.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/IdPProvisioningClientConfiguration.java similarity index 92% rename from commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/IdPProvisioningClientConfiguration.java rename to api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/IdPProvisioningClientConfiguration.java index defedf49f..30ae5b19e 100644 --- a/commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/IdPProvisioningClientConfiguration.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/IdPProvisioningClientConfiguration.java @@ -34,8 +34,9 @@ * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ -package fr.gouv.vitamui.commons.rest.client.configuration; +package fr.gouv.vitamui.iam.internal.server.provisioning.config; +import fr.gouv.vitamui.commons.rest.client.configuration.RestClientConfiguration; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -47,7 +48,7 @@ import lombok.ToString; @ToString public class IdPProvisioningClientConfiguration { - private Integer idpIdentifier; + private String idpIdentifier; private String uri; diff --git a/commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/ProvisioningClientConfiguration.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/ProvisioningClientConfiguration.java similarity index 97% rename from commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/ProvisioningClientConfiguration.java rename to api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/ProvisioningClientConfiguration.java index cc3bffb0f..2693bb797 100644 --- a/commons/commons-rest/src/main/java/fr/gouv/vitamui/commons/rest/client/configuration/ProvisioningClientConfiguration.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/config/ProvisioningClientConfiguration.java @@ -34,7 +34,7 @@ * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ -package fr.gouv.vitamui.commons.rest.client.configuration; +package fr.gouv.vitamui.iam.internal.server.provisioning.config; import java.util.List; diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalService.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalService.java index f232eaf42..0f58d0e2a 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalService.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalService.java @@ -1,25 +1,25 @@ /** * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020) * and the signatories of the "VITAM - Accord du Contributeur" agreement. - * <p> + * * contact@programmevitam.fr - * <p> + * * This software is a computer program whose purpose is to implement * implement a digital archiving front-office system for the secure and * efficient high volumetry VITAM solution. - * <p> + * * This software is governed by the CeCILL-C license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL-C * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". - * <p> + * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. - * <p> + * * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, @@ -30,7 +30,7 @@ * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. - * <p> + * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ @@ -39,16 +39,16 @@ package fr.gouv.vitamui.iam.internal.server.provisioning.service; import java.util.Optional; -import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.util.UriComponentsBuilder; -import fr.gouv.vitamui.commons.api.domain.ProvidedUserDto; +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; import fr.gouv.vitamui.commons.api.exception.NotFoundException; import fr.gouv.vitamui.commons.rest.client.BaseWebClientFactory; -import fr.gouv.vitamui.commons.rest.client.configuration.IdPProvisioningClientConfiguration; -import fr.gouv.vitamui.commons.rest.client.configuration.ProvisioningClientConfiguration; +import fr.gouv.vitamui.iam.internal.server.provisioning.config.IdPProvisioningClientConfiguration; +import fr.gouv.vitamui.iam.internal.server.provisioning.config.ProvisioningClientConfiguration; +import fr.gouv.vitamui.iam.internal.server.provisioning.client.ProvisioningWebClient; +import fr.gouv.vitamui.iam.security.service.InternalSecurityService; /** * Customer provisioning service. @@ -62,38 +62,44 @@ public class ProvisioningInternalService { private final ProvisioningClientConfiguration provisioningClientConfiguration; - public ProvisioningInternalService(final WebClient.Builder webClientBuilder, final ProvisioningClientConfiguration provisioningClientConfiguration) { + private final InternalSecurityService securityService; + + public ProvisioningInternalService(final WebClient.Builder webClientBuilder, final ProvisioningClientConfiguration provisioningClientConfiguration, + final InternalSecurityService securityService) { this.webClientBuilder = webClientBuilder; this.provisioningClientConfiguration = provisioningClientConfiguration; + this.securityService = securityService; } - public ProvidedUserDto getUserInformation(final String email, final String idp, Optional<String> groupId, Optional<String> unit, final Optional<String> technicalUserId) { - final IdPProvisioningClientConfiguration idpProvisioningClient = - provisioningClientConfiguration.getIdentityProviders().stream().filter(provisioningClient -> provisioningClient.getIdpIdentifier().equals(idp)) - .findFirst().orElseThrow(() -> new NotFoundException(String.format("Provisioning client configuration not found for IdP : {}", idp))); - final BaseWebClientFactory clientFactory = new BaseWebClientFactory(idpProvisioningClient.getClient(), webClientBuilder); + public ProvidedUserDto getUserInformation(final String idp, final String email, final String groupId, final String unit, final String technicalUserId) { + final IdPProvisioningClientConfiguration idpProvisioningClient = getProvisioningClientConfiguration(idp); - return clientFactory.getWebClient().get() - .uri(getUri(email, groupId, unit, technicalUserId, idpProvisioningClient)) - .retrieve().bodyToMono(ProvidedUserDto.class).block(); - } + var webClient = buildWebClient(idpProvisioningClient); - @NotNull - private String getUri(final String email, final Optional<String> groupId, final Optional<String> unit, final Optional<String> technicalUserId, - final IdPProvisioningClientConfiguration idpProvisioningClient) { - final UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(idpProvisioningClient.getUri()); - uriBuilder.queryParam("email", email); - if (groupId.isPresent()) { - uriBuilder.queryParam("groupId", groupId); - } - if (unit.isPresent()) { - uriBuilder.queryParam("unit", unit.get()); - } - if (technicalUserId.isPresent()) { - uriBuilder.queryParam("technicalUserId", technicalUserId.get()); - } + return webClient.getProvidedUser(securityService.getHttpContext(), email, groupId, unit, technicalUserId); + } - return uriBuilder.toUriString(); + /** + * + * @param idp + * @return + */ + protected IdPProvisioningClientConfiguration getProvisioningClientConfiguration(final String idp) { + return provisioningClientConfiguration.getIdentityProviders() + .stream() + .filter(provisioningClient -> provisioningClient.getIdpIdentifier().equals(idp)) + .findFirst().orElseThrow(() -> new NotFoundException(String.format("Provisioning client configuration not found for IdP : %S", idp))); } + /** + * Method for build webClient + * @param idpProvisioningClient + * @return + */ + protected ProvisioningWebClient buildWebClient(final IdPProvisioningClientConfiguration idpProvisioningClient) { + final BaseWebClientFactory clientFactory = new BaseWebClientFactory(idpProvisioningClient.getClient(), null ,this.webClientBuilder, + idpProvisioningClient.getUri()); + + return new ProvisioningWebClient(clientFactory.getWebClient(), idpProvisioningClient.getUri()); + } } diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/rest/CasInternalController.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/rest/CasInternalController.java index 22aaa6a35..bbe605f00 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/rest/CasInternalController.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/rest/CasInternalController.java @@ -191,8 +191,8 @@ public class CasInternalController { } @GetMapping(value = RestApi.CAS_USERS_PATH + RestApi.USERS_PROVISIONING, params = { "email", "idp" }) - public UserDto getUser(@RequestParam final String email, @RequestParam final String idp, @RequestParam final Optional<String> userIdentifier, - final @RequestParam Optional<String> embedded) { + public UserDto getUser(@RequestParam final String email, @RequestParam final String idp, @RequestParam(required = false) final String userIdentifier, + @RequestParam(required = false) final String embedded) { LOGGER.debug("getUser - email : {}, idp : {}, userIdentifier : {}, embedded options : {}", email, idp, userIdentifier, embedded); return casService.getUser(email, idp, userIdentifier, embedded); } diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/user/dao/UserRepository.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/user/dao/UserRepository.java index 6ce8a65a0..d5296ee23 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/user/dao/UserRepository.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/user/dao/UserRepository.java @@ -58,6 +58,8 @@ public interface UserRepository extends VitamUIRepository<User, String> { User findByEmail(String email); + boolean existsByEmail(String email); + long countByGroupId(String profileGroupId); Page<User> findByCustomerIdAndSubrogeableAndTypeAndStatus(String customerId, boolean subrogeable, UserTypeEnum type, UserStatusEnum status, diff --git a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalServiceTest.java b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalServiceTest.java index 94e52b8de..c5613e394 100644 --- a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalServiceTest.java +++ b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalServiceTest.java @@ -2,7 +2,6 @@ package fr.gouv.vitamui.iam.internal.server.cas.service; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -13,20 +12,23 @@ import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; import fr.gouv.vitamui.commons.api.domain.GroupDto; -import fr.gouv.vitamui.commons.api.domain.ProvidedUserDto; +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; import fr.gouv.vitamui.commons.api.domain.UserDto; -import fr.gouv.vitamui.commons.api.exception.NotFoundException; import fr.gouv.vitamui.commons.security.client.dto.AuthUserDto; import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto; import fr.gouv.vitamui.iam.internal.server.group.service.GroupInternalService; import fr.gouv.vitamui.iam.internal.server.idp.service.IdentityProviderInternalService; import fr.gouv.vitamui.iam.internal.server.provisioning.service.ProvisioningInternalService; +import fr.gouv.vitamui.iam.internal.server.user.dao.UserRepository; import fr.gouv.vitamui.iam.internal.server.user.service.UserInternalService; @ExtendWith(MockitoExtension.class) @@ -36,6 +38,8 @@ class CasInternalServiceTest { private static final String USER_EMAIL = "user@email.test"; + private static final String GROUP_ID = "groupID"; + @InjectMocks private CasInternalService casInternalService; @@ -51,19 +55,20 @@ class CasInternalServiceTest { @Mock private ProvisioningInternalService provisioningInternalService; + @Mock + private UserRepository userRepository; + @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); } - @Test - void should_return_the_user_known_in_database_when_idp_auto_provisioning_is_disabled() { - when(identityProviderInternalService.getOne(anyString())) - .thenReturn(buildIDP(false)); - + @ParameterizedTest + @NullAndEmptySource + void should_return_the_user_known_in_database_when_idp_auto_provisioning_is_disabled(String idp) { when(userInternalService.findUserByEmail(USER_EMAIL)).thenReturn(buildAuthUser(false)); - final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, Optional.empty(), Optional.empty()); + final UserDto user = casInternalService.getUser(USER_EMAIL, idp, null, null); assertThat(user).isNotNull(); } @@ -72,16 +77,17 @@ class CasInternalServiceTest { when(identityProviderInternalService.getOne(IDP)) .thenReturn(buildIDP(true)); - when(provisioningInternalService.getUserInformation(USER_EMAIL, IDP, Optional.empty(), Optional.empty(), Optional.empty())) - .thenReturn(buildProvidedUser("RH")); + when(provisioningInternalService.getUserInformation(IDP, USER_EMAIL, null, null, null)) + .thenReturn(buildProvidedUser("jean-vitam","RH")); + + when(groupInternalService.getAll(any(), any())).thenReturn(List.of(buildGroup())); - when(groupInternalService.getAll(any(), any())).thenReturn(List.of(buildProfilesGroup())); + when(userRepository.existsByEmail(ArgumentMatchers.any())).thenReturn(false); when(userInternalService.findUserByEmail(USER_EMAIL)) - .thenThrow(new NotFoundException("Not found")) .thenReturn(buildAuthUser(false)); - final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, Optional.empty(), Optional.empty()); + final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, null, null); verify(userInternalService, times(1)).create(any()); verify(userInternalService, times(0)).patch(any()); assertThat(user).isNotNull(); @@ -92,15 +98,16 @@ class CasInternalServiceTest { when(identityProviderInternalService.getOne(IDP)) .thenReturn(buildIDP(true)); - when(provisioningInternalService.getUserInformation(USER_EMAIL, IDP, Optional.empty(), Optional.empty(), Optional.empty())) - .thenReturn(buildProvidedUser("RH")); + when(provisioningInternalService.getUserInformation(IDP, USER_EMAIL, GROUP_ID, null, null)) + .thenReturn(buildProvidedUser("jean vitam", "RH")); - when(groupInternalService.getAll(any(), any())).thenReturn(List.of(buildProfilesGroup())); + when(groupInternalService.getAll(any(), any())).thenReturn(List.of(buildGroup())); + when(userRepository.existsByEmail(ArgumentMatchers.any())).thenReturn(true); when(userInternalService.findUserByEmail(USER_EMAIL)).thenReturn(buildAuthUser(true)); - final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, Optional.empty(), Optional.empty()); + final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, null, null); verify(userInternalService, times(1)).patch(any()); verify(userInternalService, times(0)).create(any()); assertThat(user).isNotNull(); @@ -111,17 +118,18 @@ class CasInternalServiceTest { when(identityProviderInternalService.getOne(IDP)) .thenReturn(buildIDP(true)); + when(userRepository.existsByEmail(ArgumentMatchers.any())).thenReturn(true); when(userInternalService.findUserByEmail(USER_EMAIL)).thenReturn(buildAuthUser(false)); - final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, Optional.empty(), Optional.empty()); + final UserDto user = casInternalService.getUser(USER_EMAIL, IDP, null, null); verify(userInternalService, times(0)).patch(any()); verify(userInternalService, times(0)).create(any()); assertThat(user).isNotNull(); } - private GroupDto buildProfilesGroup() { + private GroupDto buildGroup() { final GroupDto group = new GroupDto(); - group.setId("Group ID"); + group.setId(GROUP_ID); return group; } @@ -131,13 +139,14 @@ class CasInternalServiceTest { authUser.setFirstname("Jean-Jacques"); authUser.setLastname("Dupont"); authUser.setAutoProvisioningEnabled(autoProvisioningEnabled); + authUser.setGroupId(GROUP_ID); return authUser; } - private ProvidedUserDto buildProvidedUser(final String unit) { + private ProvidedUserDto buildProvidedUser(final String firstName, final String unit) { final ProvidedUserDto providedUser = new ProvidedUserDto(); providedUser.setEmail(USER_EMAIL); - providedUser.setFirstname("Jean-Jacques"); + providedUser.setFirstname(firstName); providedUser.setLastname("Dupont"); providedUser.setUnit(unit); return providedUser; @@ -149,4 +158,4 @@ class CasInternalServiceTest { idp.setAutoProvisioningEnabled(autoProvisioningEnabled); return idp; } -} \ No newline at end of file +} diff --git a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClientTest.java b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClientTest.java new file mode 100644 index 000000000..1b4e9cc04 --- /dev/null +++ b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/client/ProvisioningWebClientTest.java @@ -0,0 +1,119 @@ +/** + * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020) + * and the signatories of the "VITAM - Accord du Contributeur" agreement. + * <p> + * contact@programmevitam.fr + * <p> + * This software is a computer program whose purpose is to implement + * implement a digital archiving front-office system for the secure and + * efficient high volumetry VITAM solution. + * <p> + * This software is governed by the CeCILL-C license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-C + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * <p> + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * <p> + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * <p> + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +package fr.gouv.vitamui.iam.internal.server.provisioning.client; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import java.net.URI; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.reactive.function.client.WebClient; + +import fr.gouv.vitamui.commons.rest.client.InternalHttpContext; +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +class ProvisioningWebClientTest { + + @InjectMocks + private ProvisioningWebClient provisioningWebClient; + + @Mock + private WebClient webClientMock; + + @Mock + private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock; + + @Mock + private WebClient.ResponseSpec responseSpecMock; + + @Mock + private InternalHttpContext httpContextMock; + + @Test + void getProvidedUser_whenCalled_thenProvidedUserIsReturned() { + // Prepare + final var email = "email@toto.com"; + ProvidedUserDto providedUserDtoStub = new ProvidedUserDto(); + providedUserDtoStub.setEmail(email); + + mockWebClientResponse(providedUserDtoStub); + ArgumentCaptor<URI> URICaptor = ArgumentCaptor.forClass(URI.class); + + // Do + provisioningWebClient.getProvidedUser(httpContextMock,email,null,null,null); + + // Verify + verify(requestHeadersUriSpecMock, times(1)).uri(URICaptor.capture()); + URI uri = URICaptor.getValue(); + assertThat(uri.getQuery()).contains(email); + } + + private void mockWebClientResponse(final ProvidedUserDto providedUserDtoStub) { + Mono monoMock = Mockito.mock(Mono.class); + + when(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(ArgumentMatchers.any(URI.class))) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.headers(ArgumentMatchers.any())) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.retrieve()) + .thenReturn(responseSpecMock); + when(responseSpecMock.onStatus(ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenReturn(responseSpecMock); + when(responseSpecMock.bodyToMono(ProvidedUserDto.class)) + .thenReturn(monoMock); + when(monoMock.block()) + .thenReturn(providedUserDtoStub); + } + +} diff --git a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalServiceTest.java b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalServiceTest.java new file mode 100644 index 000000000..40a3ef964 --- /dev/null +++ b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/provisioning/service/ProvisioningInternalServiceTest.java @@ -0,0 +1,143 @@ +/** + * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020) + * and the signatories of the "VITAM - Accord du Contributeur" agreement. + * <p> + * contact@programmevitam.fr + * <p> + * This software is a computer program whose purpose is to implement + * implement a digital archiving front-office system for the secure and + * efficient high volumetry VITAM solution. + * <p> + * This software is governed by the CeCILL-C license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-C + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * <p> + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * <p> + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * <p> + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +package fr.gouv.vitamui.iam.internal.server.provisioning.service; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; +import java.util.Optional; + +import org.junit.Assert; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.reactive.function.client.WebClient; + +import fr.gouv.vitamui.commons.api.exception.NotFoundException; +import fr.gouv.vitamui.iam.internal.server.provisioning.config.IdPProvisioningClientConfiguration; +import fr.gouv.vitamui.iam.internal.server.provisioning.config.ProvisioningClientConfiguration; +import fr.gouv.vitamui.commons.rest.client.configuration.RestClientConfiguration; +import fr.gouv.vitamui.iam.common.dto.ProvidedUserDto; +import fr.gouv.vitamui.iam.internal.server.provisioning.client.ProvisioningWebClient; +import fr.gouv.vitamui.iam.security.service.InternalSecurityService; + +@ExtendWith(MockitoExtension.class) +class ProvisioningInternalServiceTest { + + @InjectMocks + private ProvisioningInternalService service; + + @Mock + private ProvisioningClientConfiguration provisioningClientConfigurationMock; + + @Mock + private WebClient.Builder webClientMock; + + @Mock + private InternalSecurityService securityServiceMock; + + @Nested + class getProvisioningClientConfiguration { + + @Test + void whenIdpIsUnknown_NotFoundExceptionIsThrown() { + assertThrows(NotFoundException.class, () -> { + service.getProvisioningClientConfiguration("unknowIdp"); + }); + } + + @Test + void whenIdpIsKnown_thenIdpIsReturned() { + // Prepare + var idpConfiguration = new IdPProvisioningClientConfiguration(); + idpConfiguration.setIdpIdentifier("idp"); + Mockito.when(provisioningClientConfigurationMock.getIdentityProviders()).thenReturn(Arrays.asList(idpConfiguration)); + + // Do + IdPProvisioningClientConfiguration idpFound = service.getProvisioningClientConfiguration("idp"); + + // Verify + Assert.assertEquals(idpConfiguration, idpFound); + } + + } + + @Test + void buildWebClient_whenCall_thenOk() { + // Prepare + var idpConfiguration = new IdPProvisioningClientConfiguration(); + idpConfiguration.setClient(new RestClientConfiguration()); + + // Do + ProvisioningWebClient webClient = service.buildWebClient(idpConfiguration); + + // Verify + Assert.assertNotNull(webClient); + } + + @Test + void getUserInformation_whenCall_thenUserIsReturned() { + // Prepare + var provisioningInternalServiceSpy = Mockito.spy(service); + var provisioningWebClient = Mockito.mock(ProvisioningWebClient.class); + var providedUserDtoStub = new ProvidedUserDto(); + providedUserDtoStub.setFirstname("youyou"); + + Mockito.doReturn(new IdPProvisioningClientConfiguration()).when(provisioningInternalServiceSpy).getProvisioningClientConfiguration(ArgumentMatchers.any()); + Mockito.doReturn(provisioningWebClient).when(provisioningInternalServiceSpy).buildWebClient(ArgumentMatchers.any()); + Mockito.when(provisioningWebClient.getProvidedUser(ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(providedUserDtoStub); + + // Do + ProvidedUserDto providedUserDto = + provisioningInternalServiceSpy + .getUserInformation("idp", "email@toto.com", null, null, null); + + // Verify + Mockito.verify(provisioningWebClient, Mockito.times(1)).getProvidedUser(ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()); + assertEquals(providedUserDtoStub, providedUserDto); + } +} -- GitLab