From 28eabd765d7215d097ffabd300134f8fa2a8371e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= <jerome.leleu@teamdlab.com> Date: Thu, 2 Apr 2020 18:08:41 +0200 Subject: [PATCH] password management --- cas/cas-server/pom.xml | 22 +-- .../config/cas-server-application-dev.yml | 1 + .../config/cas-server-application-recette.yml | 1 + .../fr/gouv/vitamui/cas/config/AppConfig.java | 33 +++- .../fr/gouv/vitamui/cas/config/PmConfig.java | 67 -------- .../fr/gouv/vitamui/cas/config/WebConfig.java | 79 ---------- .../vitamui/cas/config/WebflowConfig.java | 52 ++++++- ...java => IamPasswordManagementService.java} | 102 +++++------- .../fr/gouv/vitamui/cas/pm/PmTokenTicket.java | 145 ------------------ .../vitamui/cas/pm/PmTokenTicketFactory.java | 55 ------- ...tSessionTicketExpirationPolicyBuilder.java | 41 +++++ .../cas/pm/ResetPasswordController.java | 81 +++++----- .../java/fr/gouv/vitamui/cas/util/Utils.java | 26 +--- ...8NSendPasswordResetInstructionsAction.java | 79 +++++----- .../RestPasswordManagementConfiguration.java | 42 ----- .../main/resources/META-INF/spring.factories | 4 +- .../actions => }/BaseWebflowActionTest.java | 2 +- ...IamSurrogateAuthenticationServiceTest.java | 2 +- .../UserAuthenticationHandlerTest.java | 2 +- .../UserPrincipalResolverTest.java | 4 +- ... => IamPasswordManagementServiceTest.java} | 42 ++--- .../cas/provider/ProvidersServiceTest.java | 13 +- ...legatedClientAuthenticationActionTest.java | 1 + .../webflow/actions/DispatcherActionTest.java | 3 +- 24 files changed, 275 insertions(+), 624 deletions(-) delete mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/PmConfig.java delete mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java rename cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/{IamRestPasswordManagementService.java => IamPasswordManagementService.java} (64%) delete mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicket.java delete mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicketFactory.java create mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTransientSessionTicketExpirationPolicyBuilder.java delete mode 100644 cas/cas-server/src/main/java/org/apereo/cas/config/pm/RestPasswordManagementConfiguration.java rename cas/cas-server/src/test/java/fr/gouv/vitamui/cas/{webflow/actions => }/BaseWebflowActionTest.java (98%) rename cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/{IamRestPasswordManagementServiceTest.java => IamPasswordManagementServiceTest.java} (81%) diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml index 282462ba..fdec4a71 100644 --- a/cas/cas-server/pom.xml +++ b/cas/cas-server/pom.xml @@ -150,16 +150,6 @@ </dependency> <!-- password management --> - <dependency> - <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-support-pm-rest</artifactId> - <version>${cas.version}</version> - </dependency> - <dependency> - <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-support-pm</artifactId> - <version>${cas.version}</version> - </dependency> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-pm-webflow</artifactId> @@ -195,29 +185,27 @@ <version>${cas.version}</version> </dependency> - <!-- CustomInitialFlowSetupAction --> + <!-- others --> <dependency> <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-support-actions</artifactId> + <artifactId>cas-server-core-cookie-api</artifactId> <version>${cas.version}</version> </dependency> <dependency> <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-core-cookie-api</artifactId> + <artifactId>cas-server-core-web-api</artifactId> <version>${cas.version}</version> </dependency> <dependency> <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-core-web-api</artifactId> + <artifactId>cas-server-core-authentication-api</artifactId> <version>${cas.version}</version> </dependency> <dependency> <groupId>org.apereo.cas</groupId> - <artifactId>cas-server-core-authentication-api</artifactId> + <artifactId>cas-server-support-actions</artifactId> <version>${cas.version}</version> </dependency> - - <!-- others --> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-webapp-init</artifactId> diff --git a/cas/cas-server/src/main/config/cas-server-application-dev.yml b/cas/cas-server/src/main/config/cas-server-application-dev.yml index 821a84fe..74891cae 100644 --- a/cas/cas-server/src/main/config/cas-server-application-dev.yml +++ b/cas/cas-server/src/main/config/cas-server-application-dev.yml @@ -85,6 +85,7 @@ cas.authn.pm.reset.mail.from: serveur-cas@noreply.com cas.authn.pm.reset.expirationMinutes: 10 cas.authn.pm.reset.mail.attributeName: xxx cas.authn.pm.reset.securityQuestionsEnabled: false +cas.authn.pm.reset.includeServerIpAddress: false cas.authn.pm.autoLogin: true # Used to sign/encrypt the password-reset link diff --git a/cas/cas-server/src/main/config/cas-server-application-recette.yml b/cas/cas-server/src/main/config/cas-server-application-recette.yml index 31f454d4..4a9ea04f 100644 --- a/cas/cas-server/src/main/config/cas-server-application-recette.yml +++ b/cas/cas-server/src/main/config/cas-server-application-recette.yml @@ -86,6 +86,7 @@ cas.authn.pm.reset.mail.from: serveur-cas@noreply.com cas.authn.pm.reset.expirationMinutes: 10 cas.authn.pm.reset.mail.attributeName: xxx cas.authn.pm.reset.securityQuestionsEnabled: false +cas.authn.pm.reset.includeServerIpAddress: false cas.authn.pm.autoLogin: true # Used to sign/encrypt the password-reset link diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java index 2c074020..ee63213d 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java @@ -38,17 +38,22 @@ package fr.gouv.vitamui.cas.config; import fr.gouv.vitamui.cas.authentication.DelegatedSurrogateAuthenticationPostProcessor; import fr.gouv.vitamui.cas.authentication.IamSurrogateAuthenticationService; +import fr.gouv.vitamui.cas.pm.IamPasswordManagementService; import lombok.SneakyThrows; +import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.audit.AuditableExecution; import org.apereo.cas.authentication.*; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalResolver; import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService; import org.apereo.cas.configuration.CasConfigurationProperties; +import org.apereo.cas.pm.PasswordHistoryService; +import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.ticket.*; import org.apereo.cas.ticket.accesstoken.OAuth20AccessTokenFactory; import org.apereo.cas.ticket.accesstoken.OAuth20DefaultAccessToken; +import org.apereo.cas.ticket.registry.TicketRegistry; import org.apereo.cas.token.JwtBuilder; import org.apereo.cas.util.crypto.CipherExecutor; import org.pac4j.core.context.session.SessionStore; @@ -94,6 +99,9 @@ public class AppConfig extends BaseTicketCatalogConfigurer { private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(AppConfig.class); + @Autowired + private CasConfigurationProperties casProperties; + @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @@ -161,6 +169,21 @@ public class AppConfig extends BaseTicketCatalogConfigurer { @Qualifier("delegatedClientDistributedSessionStore") private SessionStore delegatedClientDistributedSessionStore; + @Autowired + private TicketRegistry ticketRegistry; + + @Autowired + @Qualifier("centralAuthenticationService") + private ObjectProvider<CentralAuthenticationService> centralAuthenticationService; + + @Autowired + @Qualifier("passwordManagementCipherExecutor") + private CipherExecutor passwordManagementCipherExecutor; + + @Autowired + @Qualifier("passwordHistoryService") + private PasswordHistoryService passwordHistoryService; + @Value("${token.api.cas}") private String tokenApiCas; @@ -239,7 +262,7 @@ public class AppConfig extends BaseTicketCatalogConfigurer { @Bean public Utils utils() { - return new Utils(casRestClient(), tokenApiCas, casTenantIdentifier, casIdentity, mailSender); + return new Utils(tokenApiCas, casTenantIdentifier, casIdentity, mailSender); } @Bean @@ -269,4 +292,12 @@ public class AppConfig extends BaseTicketCatalogConfigurer { public SurrogateAuthenticationService surrogateAuthenticationService() { return new IamSurrogateAuthenticationService(casRestClient(), servicesManager, utils()); } + + @RefreshScope + @Bean + public PasswordManagementService passwordChangeService() { + return new IamPasswordManagementService(casProperties.getAuthn().getPm(), passwordManagementCipherExecutor, + casProperties.getServer().getPrefix(), passwordHistoryService, casRestClient(), providersService(), + identityProviderHelper(), centralAuthenticationService.getObject(), utils(), ticketRegistry); + } } diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/PmConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/PmConfig.java deleted file mode 100644 index 7de12802..00000000 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/PmConfig.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 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.cas.config; - -import fr.gouv.vitamui.cas.pm.PmTokenTicket; -import fr.gouv.vitamui.cas.pm.PmTokenTicketFactory; -import org.apereo.cas.ticket.BaseTicketCatalogConfigurer; -import org.apereo.cas.ticket.TicketCatalog; -import org.apereo.cas.ticket.TicketDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Password management configuration. - * - * - */ -@Configuration -public class PmConfig extends BaseTicketCatalogConfigurer { - - @Bean - public PmTokenTicketFactory pmTokenTicketFactory() { - return new PmTokenTicketFactory(); - } - - @Override - public final void configureTicketCatalog(final TicketCatalog plan) { - final TicketDefinition ticketDefinition = buildTicketDefinition(plan, PmTokenTicket.PREFIX, PmTokenTicket.class); - ticketDefinition.getProperties().setStorageName("pmTokenTicketsCache"); - ticketDefinition.getProperties().setStorageTimeout(24L * 3600L); - plan.register(ticketDefinition); - } -} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java deleted file mode 100644 index c393d9bc..00000000 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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.cas.config; - -import fr.gouv.vitamui.cas.pm.ResetPasswordController; -import org.apereo.cas.web.view.CasProtocolView; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.thymeleaf.spring5.SpringTemplateEngine; - - -/** - * Web customizations. - * - * - */ -@Configuration -public class WebConfig { - - @Autowired - private ApplicationContext applicationContext; - - @Autowired - private SpringTemplateEngine springTemplateEngine; - - @Autowired - private ThymeleafProperties thymeleafProperties; - - @Bean - public ResetPasswordController resetPasswordController() { - return new ResetPasswordController(); - } - - @Bean - @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public CasProtocolView casGetResponseView() { - return new CasProtocolView("protocol/casGetResponseView", - applicationContext, springTemplateEngine, thymeleafProperties); - } -} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java index 36d298cf..c34f6e61 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java @@ -36,6 +36,8 @@ */ package fr.gouv.vitamui.cas.config; +import fr.gouv.vitamui.cas.pm.PmTransientSessionTicketExpirationPolicyBuilder; +import fr.gouv.vitamui.cas.pm.ResetPasswordController; import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.cas.webflow.actions.GeneralTerminateSessionAction; import fr.gouv.vitamui.cas.provider.ProvidersService; @@ -43,6 +45,7 @@ import fr.gouv.vitamui.cas.webflow.*; import fr.gouv.vitamui.cas.webflow.actions.*; import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; +import lombok.val; import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.audit.AuditableExecution; import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; @@ -51,7 +54,9 @@ import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.services.ServicesManager; -import org.apereo.cas.ticket.TicketFactory; +import org.apereo.cas.ticket.TransientSessionTicket; +import org.apereo.cas.ticket.factory.DefaultTicketFactory; +import org.apereo.cas.ticket.factory.DefaultTransientSessionTicketFactory; import org.apereo.cas.ticket.registry.TicketRegistry; import org.apereo.cas.ticket.registry.TicketRegistrySupport; import org.apereo.cas.util.CollectionUtils; @@ -63,25 +68,31 @@ import org.apereo.cas.web.flow.SingleSignOnParticipationStrategy; import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver; import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver; import org.apereo.cas.web.support.ArgumentExtractor; +import org.apereo.cas.web.view.CasProtocolView; import org.pac4j.core.client.Clients; import org.pac4j.core.context.session.SessionStore; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.ApplicationContext; +import org.springframework.context.HierarchicalMessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.execution.Action; +import org.thymeleaf.spring5.SpringTemplateEngine; /** - * Webflow customizations. + * Web(flow) customizations. * * */ @@ -169,10 +180,6 @@ public class WebflowConfig { @Qualifier("argumentExtractor") private ObjectProvider<ArgumentExtractor> argumentExtractor; - @Autowired - @Qualifier("defaultTicketFactory") - private TicketFactory ticketFactory; - @Autowired @Qualifier("centralAuthenticationService") private ObjectProvider<CentralAuthenticationService> centralAuthenticationService; @@ -194,6 +201,16 @@ public class WebflowConfig { @Autowired private TicketRegistrySupport ticketRegistrySupport; + @Autowired + @Qualifier("messageSource") + private HierarchicalMessageSource messageSource; + + @Autowired + private SpringTemplateEngine springTemplateEngine; + + @Autowired + private ThymeleafProperties thymeleafProperties; + @Value("${vitamui.portal.url}") private String vitamuiPortalUrl; @@ -206,11 +223,19 @@ public class WebflowConfig { surrogationSeparator, utils, delegatedClientDistributedSessionStore.getObject()); } + @Bean + public DefaultTransientSessionTicketFactory pmTicketFactory() { + return new DefaultTransientSessionTicketFactory(new PmTransientSessionTicketExpirationPolicyBuilder(casProperties)); + } + @Bean @RefreshScope public Action sendPasswordResetInstructionsAction() { + val pmTicketFactory = new DefaultTicketFactory(); + pmTicketFactory.addTicketFactory(TransientSessionTicket.class, pmTicketFactory()); + return new I18NSendPasswordResetInstructionsAction(casProperties, communicationsManager, passwordManagementService, - ticketRegistry, ticketFactory); + ticketRegistry, pmTicketFactory, messageSource, providersService, identityProviderHelper, utils); } @Bean @@ -268,4 +293,17 @@ public class WebflowConfig { warnCookieGenerator.getObject(), casProperties.getLogout()); } + + @Bean + @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public CasProtocolView casGetResponseView() { + return new CasProtocolView("protocol/casGetResponseView", + applicationContext, springTemplateEngine, thymeleafProperties); + } + + @Bean + public ResetPasswordController resetPasswordController() { + return new ResetPasswordController(casProperties, passwordManagementService, communicationsManager, ticketRegistry, + messageSource, utils, pmTicketFactory()); + } } diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementService.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java similarity index 64% rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementService.java rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java index e204936c..5636e5e0 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementService.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java @@ -36,10 +36,13 @@ */ package fr.gouv.vitamui.cas.pm; +import java.io.Serializable; import java.util.Map; import java.util.Optional; -import org.apereo.cas.DefaultCentralAuthenticationService; +import lombok.val; +import org.apache.commons.lang.StringUtils; +import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.Credential; import org.apereo.cas.authentication.credential.UsernamePasswordCredential; @@ -47,24 +50,22 @@ import org.apereo.cas.configuration.model.support.pm.PasswordManagementPropertie import org.apereo.cas.pm.BasePasswordManagementService; import org.apereo.cas.pm.InvalidPasswordException; import org.apereo.cas.pm.PasswordChangeRequest; +import org.apereo.cas.pm.PasswordHistoryService; import org.apereo.cas.ticket.TicketGrantingTicket; import org.apereo.cas.ticket.registry.TicketRegistry; +import org.apereo.cas.util.crypto.CipherExecutor; import org.apereo.cas.web.support.WebUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; -import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import fr.gouv.vitamui.cas.provider.ProvidersService; import fr.gouv.vitamui.cas.util.Utils; -import fr.gouv.vitamui.commons.api.domain.UserDto; import fr.gouv.vitamui.commons.api.enums.UserStatusEnum; import fr.gouv.vitamui.commons.api.exception.ConflictException; import fr.gouv.vitamui.commons.api.exception.VitamUIException; import fr.gouv.vitamui.commons.api.logger.VitamUILogger; import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; -import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto; import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; import lombok.Getter; @@ -77,11 +78,9 @@ import lombok.Setter; */ @Getter @Setter -public class IamRestPasswordManagementService extends BasePasswordManagementService { +public class IamPasswordManagementService extends BasePasswordManagementService { - private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(IamRestPasswordManagementService.class); - - private static final String PM_TICKET_ID = "pmTicketId"; + private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(IamPasswordManagementService.class); private final CasExternalRestClient casExternalRestClient; @@ -89,40 +88,43 @@ public class IamRestPasswordManagementService extends BasePasswordManagementServ private final IdentityProviderHelper identityProviderHelper; - @Autowired - private DefaultCentralAuthenticationService centralAuthenticationService; - - @Autowired - private Utils utils; + private final CentralAuthenticationService centralAuthenticationService; - @Autowired - private PmTokenTicketFactory pmTokenTicketFactory; + private final Utils utils; - @Autowired - private TicketRegistry ticketRegistry; + private final TicketRegistry ticketRegistry; - public IamRestPasswordManagementService(final CasExternalRestClient casExternalRestClient, - final PasswordManagementProperties passwordManagementProperties, - final ProvidersService providersService, - final IdentityProviderHelper identityProviderHelper) { - super(passwordManagementProperties, null, null, null); + public IamPasswordManagementService(final PasswordManagementProperties passwordManagementProperties, + final CipherExecutor<Serializable, String> cipherExecutor, + final String issuer, + final PasswordHistoryService passwordHistoryService, + final CasExternalRestClient casExternalRestClient, + final ProvidersService providersService, + final IdentityProviderHelper identityProviderHelper, + final CentralAuthenticationService centralAuthenticationService, + final Utils utils, + final TicketRegistry ticketRegistry) { + super(passwordManagementProperties, cipherExecutor, issuer, passwordHistoryService); this.casExternalRestClient = casExternalRestClient; this.providersService = providersService; this.identityProviderHelper = identityProviderHelper; + this.centralAuthenticationService = centralAuthenticationService; + this.utils = utils; + this.ticketRegistry = ticketRegistry; } protected RequestContext blockIfSubrogation() { - final RequestContext requestContext = RequestContextHolder.getRequestContext(); + val requestContext = RequestContextHolder.getRequestContext(); Authentication authentication = WebUtils.getAuthentication(requestContext); if (authentication == null) { - final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext); - if (tgtId != null) { - final TicketGrantingTicket tgt = centralAuthenticationService.getTicket(tgtId, TicketGrantingTicket.class); + val tgtId = WebUtils.getTicketGrantingTicketId(requestContext); + if (StringUtils.isNotBlank(tgtId)) { + val tgt = centralAuthenticationService.getTicket(tgtId, TicketGrantingTicket.class); authentication = tgt.getAuthentication(); } } if (authentication != null) { - final String superUsername = utils.getSuperUsername(authentication); + val superUsername = utils.getSuperUsername(authentication); Assert.isNull(superUsername, "cannot use password management with subrogation"); } @@ -131,32 +133,25 @@ public class IamRestPasswordManagementService extends BasePasswordManagementServ @Override public boolean changeInternal(final Credential c, final PasswordChangeRequest bean) throws InvalidPasswordException { - final RequestContext requestContext = blockIfSubrogation(); - final MutableAttributeMap flowScope = requestContext.getFlowScope(); + val requestContext = blockIfSubrogation(); + val flowScope = requestContext.getFlowScope(); if (flowScope != null) { flowScope.put("passwordHasBeenChanged", true); } - final UsernamePasswordCredential upc = (UsernamePasswordCredential) c; - final String username = upc.getUsername(); + val upc = (UsernamePasswordCredential) c; + val username = upc.getUsername(); Assert.notNull(username, "username can not be null"); // username to lowercase - final String usernameLowercase = username.toLowerCase(); + val usernameLowercase = username.toLowerCase(); LOGGER.debug("username: {}", usernameLowercase); - final Optional<IdentityProviderDto> identityProvider = identityProviderHelper.findByUserIdentifier(providersService.getProviders(), usernameLowercase); + val identityProvider = identityProviderHelper.findByUserIdentifier(providersService.getProviders(), usernameLowercase); Assert.isTrue(identityProvider.isPresent(), "only a user [" + usernameLowercase + "] linked to an identity provider can change his password"); Assert.isTrue(identityProvider.get().getInternal() != null && identityProvider.get().getInternal(), "only an internal user [" + usernameLowercase + "] can change his password"); - // we don't care about the fact that the oldPassword is the same as upc.getPassword(); try { casExternalRestClient.changePassword(utils.buildContext(usernameLowercase), usernameLowercase, bean.getPassword()); - - final String ticket = (String) requestContext.getFlowScope().get(PM_TICKET_ID); - if (ticket != null) { - ticketRegistry.deleteTicket(ticket); - } - return true; } catch (final ConflictException e) { @@ -171,9 +166,9 @@ public class IamRestPasswordManagementService extends BasePasswordManagementServ @Override public String findEmail(final String username) { String email = null; - final String usernameWithLowercase = username.toLowerCase(); + val usernameWithLowercase = username.toLowerCase(); try { - final UserDto user = casExternalRestClient.getUserByEmail(utils.buildContext(usernameWithLowercase), usernameWithLowercase, Optional.empty()); + val user = casExternalRestClient.getUserByEmail(utils.buildContext(usernameWithLowercase), usernameWithLowercase, Optional.empty()); if (user != null && UserStatusEnum.ENABLED.equals(user.getStatus())) { email = user.getEmail(); } @@ -189,27 +184,6 @@ public class IamRestPasswordManagementService extends BasePasswordManagementServ throw new UnsupportedOperationException("security questions/answers are not available"); } - @Override - public String createToken(final String to) { - final PmTokenTicket ticket = pmTokenTicketFactory.create(to, (int) properties.getReset().getExpirationMinutes()); - ticketRegistry.addTicket(ticket); - return ticket.getId(); - } - - @Override - public String parseToken(final String token) { - final PmTokenTicket ticket = ticketRegistry.getTicket(token, PmTokenTicket.class); - if (ticket == null || ticket.isExpired()) { - LOGGER.warn("PM token ticket expired: {}", token); - return null; - } - final RequestContext requestContext = RequestContextHolder.getRequestContext(); - if (requestContext != null) { - requestContext.getFlowScope().put(PM_TICKET_ID, ticket.getId()); - } - return ticket.getUser(); - } - private static class PasswordAlreadyUsedException extends InvalidPasswordException { /** diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicket.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicket.java deleted file mode 100644 index 5d0a943f..00000000 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicket.java +++ /dev/null @@ -1,145 +0,0 @@ -/** - * 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.cas.pm; - -import java.time.ZonedDateTime; - -import org.apereo.cas.authentication.Authentication; -import org.apereo.cas.ticket.ExpirationPolicy; -import org.apereo.cas.ticket.Ticket; -import org.apereo.cas.ticket.TicketGrantingTicket; -import org.apereo.cas.ticket.TicketState; -import org.apereo.cas.ticket.expiration.HardTimeoutExpirationPolicy; - -/** - * Specific ticket for the password management token. - * - * - */ -public class PmTokenTicket implements Ticket, TicketState { - - /** - * - */ - private static final long serialVersionUID = 4524446119459217215L; - - public static final String PREFIX = "PM"; - - private final String id; - - private final ZonedDateTime creationTime; - - private final String user; - - private final ExpirationPolicy expirationPolicy; - - public PmTokenTicket(final String id, final String user, final int ttlInMinutes) { - this.id = id; - this.user = user; - creationTime = ZonedDateTime.now(); - expirationPolicy = new HardTimeoutExpirationPolicy(ttlInMinutes * 60l); - } - - @Override - public String getId() { - return id; - } - - @Override - public boolean isExpired() { - return expirationPolicy.isExpired(this); - } - - @Override - public TicketGrantingTicket getTicketGrantingTicket() { - return null; - } - - @Override - public ZonedDateTime getCreationTime() { - return creationTime; - } - - @Override - public int getCountOfUses() { - return 0; - } - - @Override - public ExpirationPolicy getExpirationPolicy() { - return expirationPolicy; - } - - @Override - public String getPrefix() { - return PREFIX; - } - - @Override - public void markTicketExpired() { - //do nothing - } - - @Override - public int compareTo(final Ticket t) { - return creationTime.compareTo(t.getCreationTime()); - } - - public String getUser() { - return user; - } - - @Override - public ZonedDateTime getLastTimeUsed() { - return getCreationTime(); - } - - @Override - public ZonedDateTime getPreviousTimeUsed() { - return getCreationTime(); - } - - @Override - public Authentication getAuthentication() { - return null; - } - - @Override - public void update() { - //do nothing - } -} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicketFactory.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicketFactory.java deleted file mode 100644 index 10703bf7..00000000 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTokenTicketFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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.cas.pm; - -import org.apereo.cas.ticket.UniqueTicketIdGenerator; -import org.apereo.cas.util.DefaultUniqueTicketIdGenerator; - -/** - * Factory for the management password token tickets. - * - * - */ -public class PmTokenTicketFactory { - - private final UniqueTicketIdGenerator defaultTicketIdGenerator = new DefaultUniqueTicketIdGenerator(40); - - public PmTokenTicket create(final String user, final int ttlInMinutes) { - final String id = defaultTicketIdGenerator.getNewTicketId(PmTokenTicket.PREFIX); - return new PmTokenTicket(id, user, ttlInMinutes); - } -} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTransientSessionTicketExpirationPolicyBuilder.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTransientSessionTicketExpirationPolicyBuilder.java new file mode 100644 index 00000000..7b9e9107 --- /dev/null +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/PmTransientSessionTicketExpirationPolicyBuilder.java @@ -0,0 +1,41 @@ +package fr.gouv.vitamui.cas.pm; + +import fr.gouv.vitamui.commons.api.logger.VitamUILogger; +import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; +import lombok.val; +import org.apereo.cas.configuration.CasConfigurationProperties; +import org.apereo.cas.ticket.ExpirationPolicy; +import org.apereo.cas.ticket.expiration.HardTimeoutExpirationPolicy; +import org.apereo.cas.ticket.expiration.builder.TransientSessionTicketExpirationPolicyBuilder; +import org.apereo.cas.web.support.WebUtils; + +/** + * Specific expiration policy builder for password management. + */ +public class PmTransientSessionTicketExpirationPolicyBuilder extends TransientSessionTicketExpirationPolicyBuilder { + + private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(PmTransientSessionTicketExpirationPolicyBuilder.class); + + public static final String PM_EXPIRATION_IN_MINUTES_ATTRIBUTE = "pmExpirationInMinutes"; + + public PmTransientSessionTicketExpirationPolicyBuilder(final CasConfigurationProperties casProperties) { + super(casProperties); + } + + @Override + public ExpirationPolicy toTransientSessionTicketExpirationPolicy() { + val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(); + if (request != null) { + val expInMinutesAttribute = request.getAttribute(PM_EXPIRATION_IN_MINUTES_ATTRIBUTE); + if (expInMinutesAttribute != null) { + try { + val expInMinutes = Integer.parseInt((String) expInMinutesAttribute); + return new HardTimeoutExpirationPolicy(expInMinutes * 60); + } catch (final NumberFormatException e) { + LOGGER.error("Cannot get expiration in minutes", e); + } + } + } + return new HardTimeoutExpirationPolicy(casProperties.getAuthn().getPm().getReset().getExpirationMinutes() * 60); + } +} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/ResetPasswordController.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/ResetPasswordController.java index ab6e3872..80acc38d 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/ResetPasswordController.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/ResetPasswordController.java @@ -36,18 +36,21 @@ */ package fr.gouv.vitamui.cas.pm; -import static org.apereo.cas.web.flow.CasWebflowConfigurer.FLOW_ID_LOGIN; - +import java.io.Serializable; import java.util.Locale; +import lombok.RequiredArgsConstructor; +import lombok.val; import org.apache.commons.lang3.StringUtils; +import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.pm.web.flow.PasswordManagementWebflowUtils; +import org.apereo.cas.ticket.factory.DefaultTransientSessionTicketFactory; import org.apereo.cas.ticket.registry.TicketRegistry; +import org.apereo.cas.util.CollectionUtils; import org.apereo.cas.util.io.CommunicationsManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +import org.apereo.cas.web.flow.CasWebflowConfigurer; import org.springframework.context.HierarchicalMessageSource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -58,6 +61,8 @@ import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.commons.api.logger.VitamUILogger; import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; +import javax.servlet.http.HttpServletRequest; + /** * Rest controller for CAS extra features. * @@ -65,37 +70,31 @@ import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; */ @RestController @RequestMapping("/extras") +@RequiredArgsConstructor public class ResetPasswordController { private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(ResetPasswordController.class); - @Autowired - private CasConfigurationProperties casProperties; + private final CasConfigurationProperties casProperties; - @Autowired - private PasswordManagementService passwordManagementService; + private final PasswordManagementService passwordManagementService; - @Autowired - private CommunicationsManager communicationsManager; + private final CommunicationsManager communicationsManager; - @Autowired - private PmTokenTicketFactory pmTokenTicketFactory; + private final TicketRegistry ticketRegistry; - @Autowired - private TicketRegistry ticketRegistry; + private final HierarchicalMessageSource messageSource; - @Autowired - @Qualifier("messageSource") - private HierarchicalMessageSource messageSource; + private final Utils utils; - @Autowired - private Utils utils; + private final DefaultTransientSessionTicketFactory pmTicketFactory; @GetMapping("/resetPassword") public boolean resetPassword(@RequestParam(value = "username", defaultValue = "") final String username, - @RequestParam(value = "firstname", defaultValue = "") final String firstname, - @RequestParam(value = "lastname", defaultValue = "") final String lastname, @RequestParam(value = "ttl", defaultValue = "") final String ttl, - @RequestParam(value = "language", defaultValue = "en") final String language) { + @RequestParam(value = "firstname", defaultValue = "") final String firstname, + @RequestParam(value = "lastname", defaultValue = "") final String lastname, @RequestParam(value = "ttl", defaultValue = "") final String ttl, + @RequestParam(value = "language", defaultValue = "en") final String language, + final HttpServletRequest request) { if (StringUtils.isBlank(username)) { LOGGER.warn("No username is provided"); @@ -107,9 +106,9 @@ public class ResetPasswordController { LOGGER.warn("CAS is unable to send password-reset emails given no settings are defined to account for email servers"); return false; } - final String usernameLower = username.toLowerCase(); - final String to = passwordManagementService.findEmail(usernameLower); - if (StringUtils.isBlank(to)) { + val usernameLower = username.toLowerCase(); + val email = passwordManagementService.findEmail(usernameLower); + if (StringUtils.isBlank(email)) { LOGGER.warn("No recipient is provided"); return false; } @@ -117,33 +116,35 @@ public class ResetPasswordController { final int expMinutes; if (PmMessageToSend.ONE_DAY.equals(ttl)) { expMinutes = 24 * 60; - } - else { + } else { expMinutes = (int) casProperties.getAuthn().getPm().getReset().getExpirationMinutes(); } + request.setAttribute(PmTransientSessionTicketExpirationPolicyBuilder.PM_EXPIRATION_IN_MINUTES_ATTRIBUTE, expMinutes); - final String url = buildPasswordResetUrl(usernameLower, casProperties, expMinutes); + final String url = buildPasswordResetUrl(usernameLower, casProperties); final PmMessageToSend messageToSend = PmMessageToSend.buildMessage(messageSource, firstname, lastname, ttl, url, new Locale(language)); - LOGGER.debug("Generated password reset URL [{}] for: {} ({}); Link is only active for the next [{}] minute(s)", utils.sanitizePasswordResetUrl(url), to, - messageToSend.getSubject(), expMinutes); - if (!sendPasswordResetEmailToAccount(to, messageToSend.getSubject(), messageToSend.getText())) { + LOGGER.debug("Generated password reset URL [{}] for: {} ({}); Link is only active for the next [{}] minute(s)", utils.sanitizePasswordResetUrl(url), + email, messageToSend.getSubject(), expMinutes); + if (!sendPasswordResetEmailToAccount(email, messageToSend.getSubject(), messageToSend.getText())) { return false; } return true; } - protected String buildPasswordResetUrl(final String username, final CasConfigurationProperties casProperties, final int expMinutes) { - final String token = createToken(username, expMinutes); - return casProperties.getServer().getPrefix().concat('/' + FLOW_ID_LOGIN + '?' - + PasswordManagementWebflowUtils.REQUEST_PARAMETER_NAME_PASSWORD_RESET_TOKEN + '=').concat(token); - } + protected String buildPasswordResetUrl(final String username, final CasConfigurationProperties casProperties) { + val token = passwordManagementService.createToken(username); + + val properties = CollectionUtils.<String, Serializable>wrap(PasswordManagementWebflowUtils.FLOWSCOPE_PARAMETER_NAME_TOKEN, token); + val ticket = pmTicketFactory.create((Service) null, properties); + this.ticketRegistry.addTicket(ticket); + + val resetUrl = new StringBuilder(casProperties.getServer().getPrefix()) + .append('/').append(CasWebflowConfigurer.FLOW_ID_LOGIN).append('?') + .append(PasswordManagementWebflowUtils.REQUEST_PARAMETER_NAME_PASSWORD_RESET_TOKEN).append('=').append(ticket.getId()); - protected String createToken(final String to, final int expMinutes) { - final PmTokenTicket ticket = pmTokenTicketFactory.create(to, expMinutes); - ticketRegistry.addTicket(ticket); - return ticket.getId(); + return resetUrl.toString(); } protected boolean sendPasswordResetEmailToAccount(final String to, final String subject, final String msg) { diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/util/Utils.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/util/Utils.java index b3928976..3ac4fb37 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/util/Utils.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/util/Utils.java @@ -39,17 +39,15 @@ package fr.gouv.vitamui.cas.util; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.Optional; import javax.mail.internet.MimeMessage; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang.StringUtils; +import lombok.val; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.authentication.Authentication; -import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService; import org.apereo.cas.configuration.model.support.cookie.TicketGrantingCookieProperties; import org.apereo.cas.web.flow.CasWebflowConstants; @@ -65,13 +63,9 @@ import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import fr.gouv.vitamui.commons.api.CommonConstants; -import fr.gouv.vitamui.commons.api.domain.UserDto; import fr.gouv.vitamui.commons.api.logger.VitamUILogger; import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; import fr.gouv.vitamui.commons.rest.client.ExternalHttpContext; -import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; - -import lombok.val; /** * Helper class. @@ -85,8 +79,6 @@ public class Utils { private static final int BROWSER_SESSION_LIFETIME = -1; - private final CasExternalRestClient casExternalRestClient; - private final String casToken; private final Integer casTenantIdentifier; @@ -120,22 +112,6 @@ public class Utils { return username; } - public UserDto getRealUser(final Authentication authentication) { - final String username = getSuperUsername(authentication); - final UserDto user; - // it's a surrogation, we retrieve him by his email - if (StringUtils.isNotBlank(username)) { - user = casExternalRestClient.getUserByEmail(buildContext(username), username, Optional.empty()); - } - else { - // otherwise, we retrieve him by his identifier - final Principal principal = authentication.getPrincipal(); - final String userId = principal.getId(); - user = casExternalRestClient.getUserById(buildContext(userId), userId); - } - return user; - } - public Cookie buildIdpCookie(final String value, final TicketGrantingCookieProperties tgc) { final Cookie cookie = new Cookie(CommonConstants.IDP_PARAMETER, value); cookie.setPath(tgc.getPath()); diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/I18NSendPasswordResetInstructionsAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/I18NSendPasswordResetInstructionsAction.java index e40a4c64..89b321bb 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/I18NSendPasswordResetInstructionsAction.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/I18NSendPasswordResetInstructionsAction.java @@ -37,6 +37,7 @@ package fr.gouv.vitamui.cas.webflow.actions; import fr.gouv.vitamui.cas.pm.PmMessageToSend; +import fr.gouv.vitamui.cas.pm.PmTransientSessionTicketExpirationPolicyBuilder; import fr.gouv.vitamui.cas.provider.ProvidersService; import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.commons.api.logger.VitamUILogger; @@ -46,23 +47,18 @@ import lombok.val; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.authentication.credential.UsernamePasswordCredential; import org.apereo.cas.configuration.CasConfigurationProperties; -import org.apereo.cas.configuration.model.support.pm.PasswordManagementProperties; import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.pm.web.flow.actions.SendPasswordResetInstructionsAction; import org.apereo.cas.ticket.TicketFactory; import org.apereo.cas.ticket.registry.TicketRegistry; import org.apereo.cas.util.io.CommunicationsManager; import org.apereo.cas.web.support.WebUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.HierarchicalMessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; -import javax.servlet.http.HttpServletRequest; - /** * Send reset password emails with i18n messages. * @@ -73,48 +69,50 @@ public class I18NSendPasswordResetInstructionsAction extends SendPasswordResetIn private static final VitamUILogger LOGGER = VitamUILoggerFactory .getInstance(I18NSendPasswordResetInstructionsAction.class); - @Autowired - @Qualifier("messageSource") - private HierarchicalMessageSource messageSource; + private final CasConfigurationProperties casProperties; - @Autowired - private ProvidersService providersService; + private final CommunicationsManager communicationsManager; - @Autowired - private IdentityProviderHelper identityProviderHelper; + private final PasswordManagementService passwordManagementService; - @Autowired - private Utils utils; + private final HierarchicalMessageSource messageSource; - private final CasConfigurationProperties casProperties; + private final ProvidersService providersService; - private final CommunicationsManager communicationsManager; + private final IdentityProviderHelper identityProviderHelper; - private final PasswordManagementService passwordManagementService; + private final Utils utils; public I18NSendPasswordResetInstructionsAction(final CasConfigurationProperties casProperties, final CommunicationsManager communicationsManager, final PasswordManagementService passwordManagementService, final TicketRegistry ticketRegistry, - final TicketFactory ticketFactory) { + final TicketFactory ticketFactory, + final HierarchicalMessageSource messageSource, + final ProvidersService providersService, + final IdentityProviderHelper identityProviderHelper, + final Utils utils) { super(casProperties, communicationsManager, passwordManagementService, ticketRegistry, ticketFactory); this.casProperties = casProperties; this.communicationsManager = communicationsManager; this.passwordManagementService = passwordManagementService; + this.messageSource = messageSource; + this.providersService = providersService; + this.identityProviderHelper = identityProviderHelper; + this.utils = utils; } @Override protected Event doExecute(final RequestContext requestContext) { communicationsManager.validate(); if (!communicationsManager.isMailSenderDefined()) { - LOGGER.warn( - "CAS is unable to send password-reset emails given no settings are defined to account for email servers"); - return error(); + return getErrorEvent("contact.failed", "Unable to send email as no mail sender is defined", requestContext); } - final PasswordManagementProperties pm = casProperties.getAuthn().getPm(); - final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext); - // JLE: changed from CAS + + val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext); + request.removeAttribute(PmTransientSessionTicketExpirationPolicyBuilder.PM_EXPIRATION_IN_MINUTES_ATTRIBUTE); String username = request.getParameter("username"); + // added from CAS: if (StringUtils.isBlank(username)) { // try to get the username from the credentials also (after a password expiration) final MutableAttributeMap flowScope = requestContext.getFlowScope(); @@ -124,32 +122,35 @@ public class I18NSendPasswordResetInstructionsAction extends SendPasswordResetIn username = usernamePasswordCredential.getUsername(); } } - if (StringUtils.isBlank(username)) { - LOGGER.warn("No username is provided"); - return error(); + LOGGER.warn("No username parameter is provided"); + return getErrorEvent("username.required", "No username is provided", requestContext); } - // JLE: changed from CAS - final String to = passwordManagementService.findEmail(username); - if (StringUtils.isBlank(to)) { + // changed from CAS: + final String email = passwordManagementService.findEmail(username); + if (StringUtils.isBlank(email)) { LOGGER.warn("No recipient is provided; nonetheless, we return to the success page"); return success(); - } else if (!identityProviderHelper.identifierMatchProviderPattern(providersService.getProviders(), to)) { - LOGGER.warn("Recipient: {} is not internal; ignoring and returning to the success page", to); + } else if (!identityProviderHelper.identifierMatchProviderPattern(providersService.getProviders(), email)) { + LOGGER.warn("Recipient: {} is not internal; ignoring and returning to the success page", email); return success(); } val service = WebUtils.getService(requestContext); - final String url = buildPasswordResetUrl(username, passwordManagementService, casProperties, service); - - LOGGER.debug("Generated password reset URL [{}]; Link is only active for the next [{}] minute(s)", + val url = buildPasswordResetUrl(username, passwordManagementService, casProperties, service); + if (StringUtils.isNotBlank(url)) { + val pm = casProperties.getAuthn().getPm(); + LOGGER.debug("Generated password reset URL [{}]; Link is only active for the next [{}] minute(s)", utils.sanitizePasswordResetUrl(url), pm.getReset().getExpirationMinutes()); - if (sendPasswordResetEmailToAccount(to, url)) { - return success(); + if (sendPasswordResetEmailToAccount(email, url)) { + return success(); + } + } else { + LOGGER.error("No password reset URL could be built and sent to [{}]", email); } - LOGGER.error("Failed to notify account [{}]", to); - return error(); + LOGGER.error("Failed to notify account [{}]", email); + return getErrorEvent("contact.failed", "Failed to send the password reset link to the given email address or phone number", requestContext); } @Override diff --git a/cas/cas-server/src/main/java/org/apereo/cas/config/pm/RestPasswordManagementConfiguration.java b/cas/cas-server/src/main/java/org/apereo/cas/config/pm/RestPasswordManagementConfiguration.java deleted file mode 100644 index 1cdfea9d..00000000 --- a/cas/cas-server/src/main/java/org/apereo/cas/config/pm/RestPasswordManagementConfiguration.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.apereo.cas.config.pm; - -import fr.gouv.vitamui.cas.pm.IamRestPasswordManagementService; -import fr.gouv.vitamui.cas.provider.ProvidersService; -import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; -import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; -import org.apereo.cas.configuration.CasConfigurationProperties; -import org.apereo.cas.pm.PasswordManagementService; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Overrides the configuration class from the CAS server, using the CasExternalRestClient. - * - * - */ -@Configuration(value = "restPasswordManagementConfiguration", proxyBeanMethods = false) -@EnableConfigurationProperties(CasConfigurationProperties.class) -public class RestPasswordManagementConfiguration { - @Autowired - private CasConfigurationProperties casProperties; - - // customisation: - @Autowired - private CasExternalRestClient casExternalRestClient; - - @Autowired - private ProvidersService providersService; - - @Autowired - private IdentityProviderHelper identityProviderHelper; - - @RefreshScope - @Bean - public PasswordManagementService passwordChangeService() { - return new IamRestPasswordManagementService(casExternalRestClient, casProperties.getAuthn().getPm(), providersService, identityProviderHelper); - } -} diff --git a/cas/cas-server/src/main/resources/META-INF/spring.factories b/cas/cas-server/src/main/resources/META-INF/spring.factories index 9edafad4..cfdf9b2a 100644 --- a/cas/cas-server/src/main/resources/META-INF/spring.factories +++ b/cas/cas-server/src/main/resources/META-INF/spring.factories @@ -1,5 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ fr.gouv.vitamui.cas.config.AppConfig,\ -fr.gouv.vitamui.cas.config.WebConfig,\ -fr.gouv.vitamui.cas.config.WebflowConfig,\ -fr.gouv.vitamui.cas.config.PmConfig +fr.gouv.vitamui.cas.config.WebflowConfig diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/BaseWebflowActionTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/BaseWebflowActionTest.java similarity index 98% rename from cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/BaseWebflowActionTest.java rename to cas/cas-server/src/test/java/fr/gouv/vitamui/cas/BaseWebflowActionTest.java index 9d414610..c4088a49 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/BaseWebflowActionTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/BaseWebflowActionTest.java @@ -1,4 +1,4 @@ -package fr.gouv.vitamui.cas.webflow.actions; +package fr.gouv.vitamui.cas; import fr.gouv.vitamui.commons.api.identity.ServerIdentityAutoConfiguration; import org.apereo.cas.authentication.principal.WebApplicationService; diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/IamSurrogateAuthenticationServiceTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/IamSurrogateAuthenticationServiceTest.java index b94dd0ee..30266ba0 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/IamSurrogateAuthenticationServiceTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/IamSurrogateAuthenticationServiceTest.java @@ -47,7 +47,7 @@ public final class IamSurrogateAuthenticationServiceTest { public void setUp() { casExternalRestClient = mock(CasExternalRestClient.class); - val utils = new Utils(casExternalRestClient, null, 0, null, null); + val utils = new Utils(null, 0, null, null); service = new IamSurrogateAuthenticationService(casExternalRestClient, mock(ServicesManager.class), utils); } diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandlerTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandlerTest.java index eb1b350b..40559d59 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandlerTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandlerTest.java @@ -61,7 +61,7 @@ public final class UserAuthenticationHandlerTest { @Before public void setUp() { casExternalRestClient = mock(CasExternalRestClient.class); - val utils = new Utils(casExternalRestClient, null, 0, null, null); + val utils = new Utils(null, 0, null, null); handler = new UserAuthenticationHandler(null, new DefaultPrincipalFactory(), casExternalRestClient, utils, null); credential = new UsernamePasswordCredential(USERNAME, PASSWORD); } diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolverTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolverTest.java index d006f8e8..e6ac7a15 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolverTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolverTest.java @@ -2,7 +2,7 @@ package fr.gouv.vitamui.cas.authentication; import fr.gouv.vitamui.cas.util.Constants; import fr.gouv.vitamui.cas.util.Utils; -import fr.gouv.vitamui.cas.webflow.actions.BaseWebflowActionTest; +import fr.gouv.vitamui.cas.BaseWebflowActionTest; import fr.gouv.vitamui.commons.api.CommonConstants; import fr.gouv.vitamui.commons.api.domain.AddressDto; import fr.gouv.vitamui.commons.api.domain.GroupDto; @@ -75,7 +75,7 @@ public final class UserPrincipalResolverTest extends BaseWebflowActionTest { super.setUp(); casExternalRestClient = mock(CasExternalRestClient.class); - val utils = new Utils(casExternalRestClient, null, 0, null, null); + val utils = new Utils(null, 0, null, null); principalFactory = new DefaultPrincipalFactory(); sessionStore = mock(SessionStore.class); resolver = new UserPrincipalResolver(principalFactory, casExternalRestClient, utils, sessionStore); diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementServiceTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementServiceTest.java similarity index 81% rename from cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementServiceTest.java rename to cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementServiceTest.java index f609c624..04887a3c 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamRestPasswordManagementServiceTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementServiceTest.java @@ -1,25 +1,16 @@ package fr.gouv.vitamui.cas.pm; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.time.ZonedDateTime; import java.util.*; import fr.gouv.vitamui.cas.provider.ProvidersService; import fr.gouv.vitamui.cas.util.Utils; +import fr.gouv.vitamui.cas.BaseWebflowActionTest; import fr.gouv.vitamui.commons.api.domain.UserDto; import fr.gouv.vitamui.commons.api.identity.ServerIdentityAutoConfiguration; import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto; import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; +import lombok.val; import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult; import org.apereo.cas.authentication.DefaultAuthentication; import org.apereo.cas.authentication.credential.UsernamePasswordCredential; @@ -38,24 +29,25 @@ import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.webflow.core.collection.LocalAttributeMap; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.RequestContextHolder; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + /** - * Tests {@link IamRestPasswordManagementService}. + * Tests {@link IamPasswordManagementService}. * * */ @RunWith(SpringRunner.class) @ContextConfiguration(classes = ServerIdentityAutoConfiguration.class) @TestPropertySource(locations = "classpath:/application-test.properties") -public final class IamRestPasswordManagementServiceTest { +public final class IamPasswordManagementServiceTest extends BaseWebflowActionTest { private static final String EMAIL = "jerome@test.com"; - private IamRestPasswordManagementService service; + private IamPasswordManagementService service; private CasExternalRestClient casExternalRestClient; @@ -69,20 +61,16 @@ public final class IamRestPasswordManagementServiceTest { @Before public void setUp() { + super.setUp(); + casExternalRestClient = mock(CasExternalRestClient.class); providersService = mock(ProvidersService.class); identityProviderHelper = mock(IdentityProviderHelper.class); identityProviderDto = new IdentityProviderDto(); identityProviderDto.setInternal(true); when(identityProviderHelper.findByUserIdentifier(any(List.class), eq(EMAIL))).thenReturn(Optional.of(identityProviderDto)); - service = new IamRestPasswordManagementService(casExternalRestClient, null, providersService, identityProviderHelper); - final Utils utils = new Utils(casExternalRestClient, null, 0, null, null); - service.setUtils(utils); - final RequestContext context = mock(RequestContext.class); - RequestContextHolder.setRequestContext(context); - final MutableAttributeMap<Object> flowParameters = new LocalAttributeMap<>(); - when(context.getConversationScope()).thenReturn(flowParameters); - when(context.getFlowScope()).thenReturn(flowParameters); + val utils = new Utils(null, 0, null, null); + service = new IamPasswordManagementService(null, null, null, null, casExternalRestClient, providersService, identityProviderHelper, null, utils, null); final Map<String, AuthenticationHandlerExecutionResult> successes = new HashMap<>(); successes.put("fake", null); authAttributes = new HashMap<>(); @@ -188,7 +176,7 @@ public final class IamRestPasswordManagementServiceTest { } private UserDto user(final UserStatusEnum status) { - final UserDto user = new UserDto(); + val user = new UserDto(); user.setStatus(status); user.setEmail(EMAIL); return user; diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/provider/ProvidersServiceTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/provider/ProvidersServiceTest.java index c9762528..558d8381 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/provider/ProvidersServiceTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/provider/ProvidersServiceTest.java @@ -4,11 +4,11 @@ import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.commons.api.identity.ServerIdentityAutoConfiguration; import fr.gouv.vitamui.commons.rest.client.ExternalHttpContext; import fr.gouv.vitamui.iam.common.dto.common.ProviderEmbeddedOptions; -import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; import fr.gouv.vitamui.iam.external.client.IdentityProviderExternalRestClient; import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto; import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; import fr.gouv.vitamui.iam.common.utils.Saml2ClientBuilder; +import lombok.val; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,14 +50,13 @@ public final class ProvidersServiceTest { @Before public void setUp() { service = new ProvidersService(); - final Clients clients = new Clients(); + val clients = new Clients(); service.setClients(clients); - final Saml2ClientBuilder builder = mock(Saml2ClientBuilder.class); + val builder = mock(Saml2ClientBuilder.class); service.setSaml2ClientBuilder(builder); restClient = mock(IdentityProviderExternalRestClient.class); service.setIdentityProviderExternalRestClient(restClient); - final CasExternalRestClient casExternalRestClient = mock(CasExternalRestClient.class); - final Utils utils = new Utils(casExternalRestClient, null, 0, null, null); + val utils = new Utils(null, 0, null, null); service.setUtils(utils); provider = new IdentityProviderDto(); @@ -79,10 +78,10 @@ public final class ProvidersServiceTest { service.loadData(); - final Optional<IdentityProviderDto> missingProvider = identityProviderHelper.findByUserIdentifier(service.getProviders(), "jerome@vitamui.com"); + val missingProvider = identityProviderHelper.findByUserIdentifier(service.getProviders(), "jerome@vitamui.com"); assertFalse(missingProvider.isPresent()); - final Optional<IdentityProviderDto> userProvider = identityProviderHelper.findByUserIdentifier(service.getProviders(), "jerome@company.com"); + val userProvider = identityProviderHelper.findByUserIdentifier(service.getProviders(), "jerome@company.com"); assertTrue(userProvider.isPresent()); assertEquals(PROVIDER_ID, userProvider.get().getId()); assertEquals(saml2Client, ((SamlIdentityProviderDto) userProvider.get()).getSaml2Client()); diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/CustomDelegatedClientAuthenticationActionTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/CustomDelegatedClientAuthenticationActionTest.java index e79ad534..2c515caf 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/CustomDelegatedClientAuthenticationActionTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/CustomDelegatedClientAuthenticationActionTest.java @@ -5,6 +5,7 @@ import static org.mockito.Mockito.mock; import java.util.ArrayList; +import fr.gouv.vitamui.cas.BaseWebflowActionTest; import fr.gouv.vitamui.cas.provider.ProvidersService; import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; diff --git a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/DispatcherActionTest.java b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/DispatcherActionTest.java index fa96aedb..0e35e391 100644 --- a/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/DispatcherActionTest.java +++ b/cas/cas-server/src/test/java/fr/gouv/vitamui/cas/webflow/actions/DispatcherActionTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.LinkedList; import java.util.Optional; +import fr.gouv.vitamui.cas.BaseWebflowActionTest; import fr.gouv.vitamui.cas.util.Utils; import fr.gouv.vitamui.commons.api.exception.InvalidFormatException; import fr.gouv.vitamui.commons.rest.client.ExternalHttpContext; @@ -65,7 +66,7 @@ public final class DispatcherActionTest extends BaseWebflowActionTest { identityProviderHelper = mock(IdentityProviderHelper.class); casExternalRestClient = mock(CasExternalRestClient.class); - final Utils utils = new Utils(casExternalRestClient, null, 0, null, null); + final Utils utils = new Utils(null, 0, null, null); action = new DispatcherAction(providersService, identityProviderHelper, casExternalRestClient, ",", utils, mock(SessionStore.class)); final SAML2Client client = new SAML2Client(); -- GitLab