diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml index 313966d678032c0b4093cfff653cd03b69921d36..dc991abf817338f4ccb1203d818f3d45721aa2d7 100644 --- a/cas/cas-server/pom.xml +++ b/cas/cas-server/pom.xml @@ -172,11 +172,21 @@ <artifactId>cas-server-support-simple-mfa</artifactId> <version>${cas.version}</version> </dependency> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-support-simple-mfa-core</artifactId> + <version>${cas.version}</version> + </dependency> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-core-authentication-mfa</artifactId> <version>${cas.version}</version> </dependency> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-core-webflow-mfa-api</artifactId> + <version>${cas.version}</version> + </dependency> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-sms-twilio</artifactId> 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 cab87d9c917e12a88563931bda52b71906c210a9..86b862f377298aaefd969e7954c874d86d9fd2a7 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 @@ -36,8 +36,7 @@ */ 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.authentication.*; import fr.gouv.vitamui.cas.pm.IamPasswordManagementService; import lombok.SneakyThrows; import org.apereo.cas.CentralAuthenticationService; @@ -70,8 +69,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; -import fr.gouv.vitamui.cas.authentication.UserAuthenticationHandler; -import fr.gouv.vitamui.cas.authentication.UserPrincipalResolver; import fr.gouv.vitamui.cas.provider.ProvidersService; import fr.gouv.vitamui.cas.ticket.CustomOAuth20DefaultAccessTokenFactory; import fr.gouv.vitamui.cas.ticket.DynamicTicketGrantingTicketFactory; 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 9c5fe517facf3c9a09e8ba5051f7f12755c7119c..0d9265a77614240d2a858182d31a2c6c0b712ff4 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 @@ -41,8 +41,8 @@ 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; -import fr.gouv.vitamui.cas.webflow.*; import fr.gouv.vitamui.cas.webflow.actions.*; +import fr.gouv.vitamui.cas.webflow.configurer.CustomLoginWebflowConfigurer; import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper; import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; import lombok.val; @@ -52,9 +52,11 @@ import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; import org.apereo.cas.authentication.AuthenticationSystemSupport; import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy; import org.apereo.cas.configuration.CasConfigurationProperties; +import org.apereo.cas.mfa.simple.web.flow.CasSimpleMultifactorWebflowConfigurer; import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.ticket.TransientSessionTicket; +import org.apereo.cas.ticket.TransientSessionTicketFactory; import org.apereo.cas.ticket.factory.DefaultTicketFactory; import org.apereo.cas.ticket.factory.DefaultTransientSessionTicketFactory; import org.apereo.cas.ticket.registry.TicketRegistry; @@ -64,6 +66,7 @@ import org.apereo.cas.util.io.CommunicationsManager; import org.apereo.cas.web.DelegatedClientWebflowManager; import org.apereo.cas.web.cookie.CasCookieBuilder; import org.apereo.cas.web.flow.CasWebflowConfigurer; +import org.apereo.cas.web.flow.CasWebflowConstants; import org.apereo.cas.web.flow.SingleSignOnParticipationStrategy; import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver; import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver; @@ -80,12 +83,10 @@ 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.context.annotation.*; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.webflow.config.FlowDefinitionRegistryBuilder; import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.execution.Action; @@ -133,15 +134,15 @@ public class WebflowConfig { private IdentityProviderHelper identityProviderHelper; @Autowired - private FlowBuilderServices builder; + private FlowBuilderServices flowBuilderServices; @Autowired @Qualifier("logoutFlowRegistry") - private FlowDefinitionRegistry logoutFlowRegistry; + private FlowDefinitionRegistry logoutFlowDefinitionRegistry; @Autowired @Qualifier("loginFlowRegistry") - private FlowDefinitionRegistry loginFlowRegistry; + private FlowDefinitionRegistry loginFlowDefinitionRegistry; @Autowired private ApplicationContext applicationContext; @@ -211,6 +212,10 @@ public class WebflowConfig { @Autowired private ThymeleafProperties thymeleafProperties; + @Autowired + @Qualifier("casSimpleMultifactorAuthenticationTicketFactory") + private TransientSessionTicketFactory casSimpleMultifactorAuthenticationTicketFactory; + @Value("${vitamui.portal.url}") private String vitamuiPortalUrl; @@ -252,8 +257,8 @@ public class WebflowConfig { @Order(0) @RefreshScope public CasWebflowConfigurer defaultWebflowConfigurer() { - final CustomLoginWebflowConfigurer c = new CustomLoginWebflowConfigurer(builder, loginFlowRegistry, applicationContext, casProperties); - c.setLogoutFlowDefinitionRegistry(logoutFlowRegistry); + final CustomLoginWebflowConfigurer c = new CustomLoginWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry, applicationContext, casProperties); + c.setLogoutFlowDefinitionRegistry(logoutFlowDefinitionRegistry); c.setOrder(Ordered.HIGHEST_PRECEDENCE); return c; } @@ -309,6 +314,41 @@ public class WebflowConfig { @Bean public Action loadSurrogatesListAction() { - return new AlwaysSuccessAction(); + return new NoOpAction("success"); + } + + @Bean + @RefreshScope + public Action mfaSimpleMultifactorSendTokenAction() { + val simple = casProperties.getAuthn().getMfa().getSimple(); + return new CustomSendTokenAction(ticketRegistry, communicationsManager, + casSimpleMultifactorAuthenticationTicketFactory, simple, utils); + } + + @Bean + public FlowDefinitionRegistry customMfaSimpleAuthenticatorFlowRegistry() { + val builder = new FlowDefinitionRegistryBuilder(this.applicationContext, this.flowBuilderServices); + builder.setBasePath(CasWebflowConstants.BASE_CLASSPATH_WEBFLOW); + builder.addFlowLocationPattern("/mfa-simple/mfa-simple-custom-webflow.xml"); + return builder.build(); + } + + @Bean + @DependsOn("defaultWebflowConfigurer") + public CasWebflowConfigurer mfaSimpleMultifactorWebflowConfigurer() { + return new CasSimpleMultifactorWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry, + customMfaSimpleAuthenticatorFlowRegistry(), applicationContext, casProperties); + } + + @Bean + public Action checkMfaTokenAction() { + return new CheckMfaTokenAction(ticketRegistry); + } + + @Bean + @Lazy + @RefreshScope + public Action delegatedAuthenticationClientLogoutAction() { + return new NoOpAction(null); } } 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 index 7b9e9107ff7b12267181c4aa37b731593836e42b..e47c96ad3b4210e6ba1eb460227dc2d810abb535 100644 --- 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 @@ -1,3 +1,39 @@ +/** + * 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 fr.gouv.vitamui.commons.api.logger.VitamUILogger; diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/AlwaysSuccessAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/AlwaysSuccessAction.java deleted file mode 100644 index 523e3870db0a0901297e95353c3ec1d71c00a80f..0000000000000000000000000000000000000000 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/AlwaysSuccessAction.java +++ /dev/null @@ -1,16 +0,0 @@ -package fr.gouv.vitamui.cas.webflow.actions; - -import org.springframework.webflow.action.AbstractAction; -import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.RequestContext; - -/** - * An always "success" action. - */ -public class AlwaysSuccessAction extends AbstractAction { - - @Override - protected Event doExecute(final RequestContext requestContext) { - return success(); - } -} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CheckMfaTokenAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CheckMfaTokenAction.java new file mode 100644 index 0000000000000000000000000000000000000000..508107480033cad86088b2da82a5258640da8114 --- /dev/null +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CheckMfaTokenAction.java @@ -0,0 +1,82 @@ +/** + * 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.webflow.actions; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apereo.cas.mfa.simple.CasSimpleMultifactorAuthenticationTicketFactory; +import org.apereo.cas.mfa.simple.CasSimpleMultifactorTokenCredential; +import org.apereo.cas.ticket.TransientSessionTicket; +import org.apereo.cas.ticket.registry.TicketRegistry; +import org.apereo.cas.web.support.WebUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * Check the MFA token. + */ +@RequiredArgsConstructor +@Slf4j +public class CheckMfaTokenAction extends AbstractAction { + + private final TicketRegistry ticketRegistry; + + @Override + protected Event doExecute(final RequestContext requestContext) { + val credential = WebUtils.getCredential(requestContext); + val tokenCredential = (CasSimpleMultifactorTokenCredential) credential; + val token = CasSimpleMultifactorAuthenticationTicketFactory.PREFIX + "-" + tokenCredential.getToken(); + LOGGER.debug("Checking token: {}", token); + WebUtils.putCredential(requestContext, new CasSimpleMultifactorTokenCredential(token)); + + val acct = this.ticketRegistry.getTicket(token, TransientSessionTicket.class); + if (acct != null) { + val creationTime = acct.getCreationTime(); + val now_less_one_minute = ZonedDateTime.now().minus(60, ChronoUnit.SECONDS); + // considered expired after 60 seconds + if (creationTime.isBefore(now_less_one_minute)) { + return error(); + } + } + return success(); + } +} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CustomSendTokenAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CustomSendTokenAction.java new file mode 100644 index 0000000000000000000000000000000000000000..a2c46c79771a090df63dad64a6a83ddb9154fd7b --- /dev/null +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CustomSendTokenAction.java @@ -0,0 +1,146 @@ +/** + * 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.webflow.actions; + +import fr.gouv.vitamui.cas.util.Utils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.commons.lang3.StringUtils; +import org.apereo.cas.authentication.principal.Principal; +import org.apereo.cas.configuration.model.support.mfa.CasSimpleMultifactorProperties; +import org.apereo.cas.mfa.simple.CasSimpleMultifactorAuthenticationHandler; +import org.apereo.cas.mfa.simple.CasSimpleMultifactorAuthenticationTicketFactory; +import org.apereo.cas.ticket.Ticket; +import org.apereo.cas.ticket.TransientSessionTicketFactory; +import org.apereo.cas.ticket.registry.TicketRegistry; +import org.apereo.cas.util.CollectionUtils; +import org.apereo.cas.util.io.CommunicationsManager; +import org.apereo.cas.web.flow.CasWebflowConstants; +import org.apereo.cas.web.support.WebUtils; +import org.springframework.binding.message.MessageBuilder; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.action.EventFactorySupport; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +@Slf4j +@RequiredArgsConstructor +public class CustomSendTokenAction extends AbstractAction { + private static final String MESSAGE_MFA_TOKEN_SENT = "cas.mfa.simple.label.tokensent"; + + private final TicketRegistry ticketRegistry; + private final CommunicationsManager communicationsManager; + private final TransientSessionTicketFactory ticketFactory; + private final CasSimpleMultifactorProperties properties; + private final Utils utils; + + @Override + protected Event doExecute(final RequestContext requestContext) { + val authentication = WebUtils.getInProgressAuthentication(); + val principal = authentication.getPrincipal(); + val principalAttributes = principal.getAttributes(); + + // custo + val mobile = (String) utils.getAttributeValue(principalAttributes, "mobile"); + if (mobile == null) { + requestContext.getFlowScope().put("firstname", utils.getAttributeValue(principalAttributes, "firstname")); + return getEventFactorySupport().event(this, "missingPhone"); + } + + val service = WebUtils.getService(requestContext); + val token = ticketFactory.create(service, CollectionUtils.wrap(CasSimpleMultifactorAuthenticationHandler.PROPERTY_PRINCIPAL, principal)); + LOGGER.debug("Created multifactor authentication token [{}] for service [{}]", token, service); + + val smsSent = isSmsSent(communicationsManager, properties, principal, token); + val emailSent = isMailSent(communicationsManager, properties, principal, token); + + if (smsSent || emailSent) { + ticketRegistry.addTicket(token); + LOGGER.debug("Successfully submitted token via SMS and/or email to [{}]", principal.getId()); + + val resolver = new MessageBuilder() + .info() + .code(MESSAGE_MFA_TOKEN_SENT) + .defaultText(MESSAGE_MFA_TOKEN_SENT) + .build(); + requestContext.getMessageContext().addMessage(resolver); + + // custo + requestContext.getFlowScope().put("mobile", obfuscateMobile(mobile)); + + val attributes = new LocalAttributeMap("token", token.getId()); + return new EventFactorySupport().event(this, CasWebflowConstants.TRANSITION_ID_SUCCESS, attributes); + } + LOGGER.error("Both email and SMS communication strategies failed to submit token [{}] to user", token); + return error(); + } + + private String obfuscateMobile(final String mobile) { + String m = mobile.replaceFirst("\\+33", "0"); + m = m.substring(0, 2) + " XX XX XX " + m.substring(m.length() - 2); + return m; + } + + private boolean isSmsSent(final CommunicationsManager communicationsManager, + final CasSimpleMultifactorProperties properties, + final Principal principal, + final Ticket token) { + if (communicationsManager.isSmsSenderDefined()) { + val smsProperties = properties.getSms(); + String smsText = StringUtils.isNotBlank(smsProperties.getText()) + ? smsProperties.getFormattedText(token.getId()) + : token.getId(); + // custo: remove the CAS prefix + smsText = smsText.replace(CasSimpleMultifactorAuthenticationTicketFactory.PREFIX + "-", ""); + return communicationsManager.sms(principal, smsProperties.getAttributeName(), smsText, smsProperties.getFrom()); + } + return false; + } + + private boolean isMailSent(final CommunicationsManager communicationsManager, + final CasSimpleMultifactorProperties properties, + final Principal principal, + final Ticket token) { + if (communicationsManager.isMailSenderDefined()) { + val mailProperties = properties.getMail(); + return communicationsManager.email(principal, mailProperties.getAttributeName(), mailProperties, mailProperties.getFormattedBody(token.getId())); + } + return false; + } +} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/NoOpAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/NoOpAction.java new file mode 100644 index 0000000000000000000000000000000000000000..7f80f6f16884d4c7e71ef8f215bb682504f13017 --- /dev/null +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/NoOpAction.java @@ -0,0 +1,59 @@ +/** + * 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.webflow.actions; + +import lombok.RequiredArgsConstructor; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +/** + * A no-op action returning a specific result. + */ +@RequiredArgsConstructor +public class NoOpAction extends AbstractAction { + + private final String eventId; + + @Override + protected Event doExecute(final RequestContext requestContext) { + if (eventId != null) { + return getEventFactorySupport().event(this, eventId); + } + return null; + } +} diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/SelectRedirectAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/SelectRedirectAction.java index ff25f295c28d6bc02a72fd64f2f3030dcee29599..64fb2f2a0a9280584092210d5281164340dfc8e5 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/SelectRedirectAction.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/SelectRedirectAction.java @@ -36,7 +36,7 @@ */ package fr.gouv.vitamui.cas.webflow.actions; -import fr.gouv.vitamui.cas.webflow.CustomLoginWebflowConfigurer; +import fr.gouv.vitamui.cas.webflow.configurer.CustomLoginWebflowConfigurer; import fr.gouv.vitamui.commons.api.logger.VitamUILogger; import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory; import lombok.RequiredArgsConstructor; diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/CustomLoginWebflowConfigurer.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/configurer/CustomLoginWebflowConfigurer.java similarity index 99% rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/CustomLoginWebflowConfigurer.java rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/configurer/CustomLoginWebflowConfigurer.java index 77b5f66152642146007fa2300430ba20327c7603..022053f2650d8dbf362b215c892cb0992792ab4a 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/CustomLoginWebflowConfigurer.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/configurer/CustomLoginWebflowConfigurer.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.cas.webflow; +package fr.gouv.vitamui.cas.webflow.configurer; import javax.security.auth.login.AccountLockedException; import javax.security.auth.login.AccountNotFoundException; @@ -61,7 +61,6 @@ import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.ActionState; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.History; -import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import fr.gouv.vitamui.cas.webflow.actions.TriggerChangePasswordAction; diff --git a/cas/cas-server/src/main/resources/templates/casSmsLoginView.html b/cas/cas-server/src/main/resources/templates/casSimpleMfaLoginView.html similarity index 100% rename from cas/cas-server/src/main/resources/templates/casSmsLoginView.html rename to cas/cas-server/src/main/resources/templates/casSimpleMfaLoginView.html diff --git a/cas/cas-server/src/main/resources/templates/casSurrogateAuthnListView.html b/cas/cas-server/src/main/resources/templates/casSurrogateAuthnListView.html deleted file mode 100644 index cc60e94b64483ecef114bda1289e67bfbf5b149a..0000000000000000000000000000000000000000 --- a/cas/cas-server/src/main/resources/templates/casSurrogateAuthnListView.html +++ /dev/null @@ -1,45 +0,0 @@ -<!DOCTYPE html> -<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}"> - -<head> - <title th:text="#{screen.surrogates.account.selection.header}">Surrogate Account Selection View</title> -</head> - -<body id="cas" th:styleappend="${@environment.getProperty('theme.body.style')}"> - <div layout:fragment="content"> - <div class="alert alert-info"> - <form method="post" id="loginForm" class="fm-v clearfix"> - <div class="form-group"> - <h2 th:text="#{screen.surrogates.choose.account}">Choose Account</h2> - <div th:utext="#{screen.surrogates.message}"> - <p>You are provided with a list of accounts on behalf of which you are allowed to authenticate. </p> - <p>Select one and continue.</p> - </div> - <label for="surrogateTarget" class="fl-label">Account:</label> - <select name="surrogateTarget" id="surrogateTarget" class="form-control"> - <option th:each="surrogate: ${surrogates}" th:value="${surrogate}" th:text="${surrogate}"/> - </select> - - - </div> - <input type="hidden" name="execution" th:value="${flowExecutionKey}"/> - <input type="hidden" name="_eventId" value="submit"/> - <input class="btn btn-submit" name="submit" accesskey="l" - th:value="#{screen.welcome.button.login}" type="submit" value="Login"/> - - <a class="btn btn-danger" id="cancel" name="cancel" - th:unless="${service}" - th:href="@{/login}" th:text="#{screen.surrogates.button.cancel}">Cancel</a> - <a class="btn btn-primary" id="login" name="login" - th:if="${service}" - th:href="${service.id}" th:text="#{screen.error.page.loginagain}">Login</a> - - </form> - </div> - - <button class="back" type="button" th:onclick="|location.href = '@{/login}';|"> - <i class="material-icons">arrow_back</i> Retour - </button> - </div> -</body> -</html> diff --git a/cas/cas-server/src/main/resources/webflow/mfa-simple/mfa-simple-custom-webflow.xml b/cas/cas-server/src/main/resources/webflow/mfa-simple/mfa-simple-custom-webflow.xml new file mode 100644 index 0000000000000000000000000000000000000000..412b7296e7cbd2701c9ee37a89331081ede406f3 --- /dev/null +++ b/cas/cas-server/src/main/resources/webflow/mfa-simple/mfa-simple-custom-webflow.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<flow xmlns="http://www.springframework.org/schema/webflow" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/webflow + http://www.springframework.org/schema/webflow/spring-webflow.xsd"> + + <var name="credential" class="org.apereo.cas.mfa.simple.CasSimpleMultifactorTokenCredential" /> + <on-start> + <evaluate expression="initialFlowSetupAction" /> + </on-start> + + <action-state id="initializeLoginForm"> + <evaluate expression="initializeLoginAction" /> + <transition on="success" to="sendSimpleToken"/> + </action-state> + + <action-state id="sendSimpleToken"> + <evaluate expression="mfaSimpleMultifactorSendTokenAction" /> + <transition on="success" to="viewLoginForm"/> + <transition on="error" to="unavailable"/> + <!-- custo --> + <transition on="missingPhone" to="missingPhone"/> + </action-state> + + <!-- custo --> + <view-state id="missingPhone" view="casSmsMissingPhoneView" /> + + <view-state id="viewLoginForm" view="casSimpleMfaLoginView" model="credential"> + <binder> + <binding property="token" required="true"/> + </binder> + <on-entry> + <set name="viewScope.principal" value="conversationScope.authentication.principal" /> + </on-entry> + <!-- custo --> + <transition on="submit" bind="true" validate="true" to="intermediateSubmit"/> + <transition on="resend" bind="false" validate="false" to="sendSimpleToken"/> + + </view-state> + + <!-- custo --> + <action-state id="intermediateSubmit"> + <evaluate expression="checkMfaTokenAction" /> + <transition on="success" to="realSubmit"/> + <transition on="error" to="codeExpired"/> + </action-state> + + <!-- custo --> + <view-state id="codeExpired" view="casSmsCodeExpiredView"> + <transition on="resend" to="initializeLoginForm"/> + </view-state> + + <action-state id="realSubmit"> + <evaluate expression="oneTimeTokenAuthenticationWebflowAction" /> + <transition on="success" to="success" /> + <transition on="error" to="viewLoginForm" /> + </action-state> + + <end-state id="success" /> + <end-state id="unavailable" /> +</flow>