From fef548c7546d18bbdd9e2b47fb996699b05894cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= <jerome.leleu@teamdlab.com>
Date: Fri, 17 Apr 2020 17:33:52 +0200
Subject: [PATCH] MFA works

---
 cas/cas-server/pom.xml                        |  10 ++
 .../fr/gouv/vitamui/cas/config/AppConfig.java |   5 +-
 .../vitamui/cas/config/WebflowConfig.java     |  62 ++++++--
 ...tSessionTicketExpirationPolicyBuilder.java |  36 +++++
 .../webflow/actions/AlwaysSuccessAction.java  |  16 --
 .../webflow/actions/CheckMfaTokenAction.java  |  82 ++++++++++
 .../actions/CustomSendTokenAction.java        | 146 ++++++++++++++++++
 .../cas/webflow/actions/NoOpAction.java       |  59 +++++++
 .../webflow/actions/SelectRedirectAction.java |   2 +-
 .../CustomLoginWebflowConfigurer.java         |   3 +-
 ...inView.html => casSimpleMfaLoginView.html} |   0
 .../templates/casSurrogateAuthnListView.html  |  45 ------
 .../mfa-simple/mfa-simple-custom-webflow.xml  |  61 ++++++++
 13 files changed, 448 insertions(+), 79 deletions(-)
 delete mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/AlwaysSuccessAction.java
 create mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CheckMfaTokenAction.java
 create mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/CustomSendTokenAction.java
 create mode 100644 cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/actions/NoOpAction.java
 rename cas/cas-server/src/main/java/fr/gouv/vitamui/cas/webflow/{ => configurer}/CustomLoginWebflowConfigurer.java (99%)
 rename cas/cas-server/src/main/resources/templates/{casSmsLoginView.html => casSimpleMfaLoginView.html} (100%)
 delete mode 100644 cas/cas-server/src/main/resources/templates/casSurrogateAuthnListView.html
 create mode 100644 cas/cas-server/src/main/resources/webflow/mfa-simple/mfa-simple-custom-webflow.xml

diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml
index 313966d6..dc991abf 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 cab87d9c..86b862f3 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 9c5fe517..0d9265a7 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 7b9e9107..e47c96ad 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 523e3870..00000000
--- 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 00000000..50810748
--- /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 00000000..a2c46c79
--- /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 00000000..7f80f6f1
--- /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 ff25f295..64fb2f2a 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 77b5f661..022053f2 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 cc60e94b..00000000
--- 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"/>
-
-                &nbsp;<a class="btn btn-danger" id="cancel" name="cancel"
-                         th:unless="${service}"
-                         th:href="@{/login}" th:text="#{screen.surrogates.button.cancel}">Cancel</a>
-                &nbsp;<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 00000000..412b7296
--- /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>
-- 
GitLab