From 0d79fe606d8d38589cc4a35167d616e7755b411f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= <jerome.leleu@teamdlab.com>
Date: Fri, 10 Apr 2020 18:09:35 +0200
Subject: [PATCH] WIP MFA

---
 cas/cas-server/pom.xml                        |  5 ++++
 .../config/cas-server-application-dev.yml     | 30 +++++++------------
 .../config/cas-server-application-recette.yml | 30 +++++++------------
 .../authentication/UserPrincipalResolver.java | 13 +++++++-
 .../fr/gouv/vitamui/cas/config/AppConfig.java |  3 +-
 .../UserPrincipalResolverTest.java            |  5 +++-
 6 files changed, 45 insertions(+), 41 deletions(-)

diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml
index fdec4a71..313966d6 100644
--- a/cas/cas-server/pom.xml
+++ b/cas/cas-server/pom.xml
@@ -167,6 +167,11 @@
         </dependency>
 
         <!-- multi-factor authentication -->
+        <dependency>
+            <groupId>org.apereo.cas</groupId>
+            <artifactId>cas-server-support-simple-mfa</artifactId>
+            <version>${cas.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apereo.cas</groupId>
             <artifactId>cas-server-core-authentication-mfa</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 74891cae..61763f5d 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
@@ -64,34 +64,29 @@ cas.serviceRegistry.mongo.collection: services
 
 
 cas.authn.surrogate.separator: ","
-## Useless because of IamSurrogateRestAuthenticationService:
-# cas.authn.surrogate.rest.url: xxx
-# cas.authn.surrogate.rest.method: GET
 cas.authn.surrogate.sms.attributeName: fakeNameToBeSureToFindNoAttributeAndNeverSendAnSMS
 
 
 cas.authn.pm.enabled: true
-
-## Useless because of IamRestPasswordManagementService:
-# cas.authn.pm.rest.endpointUrlEmail: xxx
-# cas.authn.pm.rest.endpointUrlSecurityQuestions: xxx
-# cas.authn.pm.rest.endpointUrlChange: xxx
-
 cas.authn.pm.policyPattern: '^(?=.*[$@!%*#?&=\-\/:;\(\)"\.,\?!''\[\]{}^\+\=_\\\|~<>`])(?=.*[\d])[A-Za-zÀ-ÿ0-9$@!%*#?&=\-\/:;\(\)"\.,\?!''\[\]{}^\+\=_\\\|~<>`]{8,}$'
-
 cas.authn.pm.reset.mail.subject: Requete de reinitialisation de mot de passe
 cas.authn.pm.reset.mail.text: "Changez de mot de passe via le lien: %s"
 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.mail.attributeName: email
 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
-# cas.authn.pm.reset.crypto.encryption.key:
-# cas.authn.pm.reset.crypto.signing.key:
-# cas.authn.pm.reset.crypto.enabled: true
+
+cas.authn.mfa.simple.sms.from: '+33644602712'
+cas.authn.mfa.simple.sms.text: 'Code : %s'
+cas.authn.mfa.simple.sms.attributeName: mobile
+cas.authn.mfa.simple.timeToKillInSeconds: 3600
+cas.authn.mfa.simple.tokenLength: 4
+cas.authn.mfa.globalPrincipalAttributeNameTriggers: computedOtp
+cas.authn.mfa.globalPrincipalAttributeValueRegex: 'true'
+cas.authn.mfa.simple.mail.text: xxx
 
 
 spring.mail.host: smtp.gmail.com
@@ -103,9 +98,6 @@ spring.mail.properties.mail.smtp.auth: true
 spring.mail.properties.mail.smtp.starttls.enable: true
 
 
-#cas.authn.mfa.globalProviderId: mfa-simple
-
-
 cas.authn.throttle.failure.threshold: 2
 cas.authn.throttle.failure.rangeSeconds: 3
 
@@ -123,10 +115,10 @@ management.endpoints.enabled-by-default: true
 #management.metrics.export.prometheus.enabled: true
 cas.monitor.endpoints.endpoint.defaults.access[0]: ANONYMOUS
 
+
 # for SMS:
 cas.smsProvider.twilio.accountId: AC3942c2fee9478d0295b3051735860e3b
 cas.smsProvider.twilio.token: 982e4b1cffaaaac491305d984d43df9f
-mfa.sms.sender: "+33644602712"
 
 
 vitamui.portal.url: https://dev.vitamui.com:4200/
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 4a9ea04f..59ebda7c 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
@@ -65,34 +65,29 @@ cas.serviceRegistry.mongo.collection: services
 
 
 cas.authn.surrogate.separator: ","
-## Useless because of IamSurrogateRestAuthenticationService:
-# cas.authn.surrogate.rest.url: xxx
-# cas.authn.surrogate.rest.method: GET
 cas.authn.surrogate.sms.attributeName: fakeNameToBeSureToFindNoAttributeAndNeverSendAnSMS
 
 
 cas.authn.pm.enabled: true
-
-## Useless because of IamRestPasswordManagementService:
-# cas.authn.pm.rest.endpointUrlEmail: xxx
-# cas.authn.pm.rest.endpointUrlSecurityQuestions: xxx
-# cas.authn.pm.rest.endpointUrlChange: xxx
-
 cas.authn.pm.policyPattern: '^(?=.*[$@!%*#?&=\-\/:;\(\)"\.,\?!''\[\]{}^\+\=_\\\|~<>`])(?=.*[\d])[A-Za-zÀ-ÿ0-9$@!%*#?&=\-\/:;\(\)"\.,\?!''\[\]{}^\+\=_\\\|~<>`]{8,}$'
-
 cas.authn.pm.reset.mail.subject: Requete de reinitialisation de mot de passe
 cas.authn.pm.reset.mail.text: "Changez de mot de passe via le lien: %s"
 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.mail.attributeName: email
 cas.authn.pm.reset.securityQuestionsEnabled: false
 cas.authn.pm.reset.includeServerIpAddress: false
 cas.authn.pm.autoLogin: true
+cas.authn.mfa.simple.mail.text: xxx
+
 
-# Used to sign/encrypt the password-reset link
-# cas.authn.pm.reset.crypto.encryption.key:
-# cas.authn.pm.reset.crypto.signing.key:
-# cas.authn.pm.reset.crypto.enabled: true
+cas.authn.mfa.simple.sms.from: '+33644602712'
+cas.authn.mfa.simple.sms.text: 'Code : %s'
+cas.authn.mfa.simple.sms.attributeName: mobile
+cas.authn.mfa.simple.timeToKillInSeconds: 3600
+cas.authn.mfa.simple.tokenLength: 4
+cas.authn.mfa.globalPrincipalAttributeNameTriggers: computedOtp
+cas.authn.mfa.globalPrincipalAttributeValueRegex: 'true'
 
 
 spring.mail.host: smtp.gmail.com
@@ -104,9 +99,6 @@ spring.mail.properties.mail.smtp.auth: true
 spring.mail.properties.mail.smtp.starttls.enable: true
 
 
-#cas.authn.mfa.globalProviderId: mfa-simple
-
-
 cas.authn.throttle.failure.threshold: 2
 cas.authn.throttle.failure.rangeSeconds: 3
 
@@ -124,10 +116,10 @@ management.endpoints.enabled-by-default: true
 #management.metrics.export.prometheus.enabled: true
 cas.monitor.endpoints.endpoint.defaults.access[0]: ANONYMOUS
 
+
 # for SMS:
 cas.smsProvider.twilio.accountId: AC3942c2fee9478d0295b3051735860e3b
 cas.smsProvider.twilio.token: 982e4b1cffaaaac491305d984d43df9f
-mfa.sms.sender: "+33644602712"
 
 
 vitamui.portal.url: https://dev.vitamui.com/
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
index 0aa50da2..2d164516 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
@@ -71,6 +71,8 @@ import static fr.gouv.vitamui.commons.api.CommonConstants.USER_ID_ATTRIBUTE;
 
 import java.util.*;
 
+import fr.gouv.vitamui.cas.provider.ProvidersService;
+import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
 import lombok.RequiredArgsConstructor;
 import org.apereo.cas.authentication.AuthenticationHandler;
 import org.apereo.cas.authentication.Credential;
@@ -108,6 +110,7 @@ import lombok.val;
 public class UserPrincipalResolver implements PrincipalResolver {
 
     public static final String SUPER_USER_ID_ATTRIBUTE = "superUserId";
+    public static final String COMPUTED_OTP = "computedOtp";
 
     private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(UserPrincipalResolver.class);
 
@@ -119,6 +122,10 @@ public class UserPrincipalResolver implements PrincipalResolver {
 
     private final SessionStore sessionStore;
 
+    private final IdentityProviderHelper identityProviderHelper;
+
+    private final ProvidersService providersService;
+
     @Override
     public Principal resolve(final Credential credential, final Optional<Principal> principal, final Optional<AuthenticationHandler> handler) {
 
@@ -178,7 +185,11 @@ public class UserPrincipalResolver implements PrincipalResolver {
         attributes.put(FIRSTNAME_ATTRIBUTE, Collections.singletonList(user.getFirstname()));
         attributes.put(LASTNAME_ATTRIBUTE, Collections.singletonList(user.getLastname()));
         attributes.put(IDENTIFIER_ATTRIBUTE, Collections.singletonList(user.getIdentifier()));
-        attributes.put(OTP_ATTRIBUTE, Collections.singletonList(user.isOtp()));
+        val otp = user.isOtp();
+        attributes.put(OTP_ATTRIBUTE, Collections.singletonList(otp));
+        val otpUsername = superUsername != null ? superUsername : username;
+        val computedOtp = otp && identityProviderHelper.identifierMatchProviderPattern(providersService.getProviders(), otpUsername);
+        attributes.put(COMPUTED_OTP, Collections.singletonList("" + computedOtp));
         attributes.put(SUBROGEABLE_ATTRIBUTE, Collections.singletonList(user.isSubrogeable()));
         attributes.put(LANGUAGE_ATTRIBUTE, Collections.singletonList(user.getLanguage()));
         attributes.put(PHONE_ATTRIBUTE, Collections.singletonList(user.getPhone()));
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 ee63213d..cab87d9c 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
@@ -207,7 +207,8 @@ public class AppConfig extends BaseTicketCatalogConfigurer {
     @Bean
     @RefreshScope
     public PrincipalResolver surrogatePrincipalResolver() {
-        return new UserPrincipalResolver(principalFactory, casRestClient(), utils(), delegatedClientDistributedSessionStore);
+        return new UserPrincipalResolver(principalFactory, casRestClient(), utils(), delegatedClientDistributedSessionStore,
+            identityProviderHelper(), providersService());
     }
 
     @Bean
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 e6ac7a15..81992b72 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
@@ -1,5 +1,6 @@
 package fr.gouv.vitamui.cas.authentication;
 
+import fr.gouv.vitamui.cas.provider.ProvidersService;
 import fr.gouv.vitamui.cas.util.Constants;
 import fr.gouv.vitamui.cas.util.Utils;
 import fr.gouv.vitamui.cas.BaseWebflowActionTest;
@@ -14,6 +15,7 @@ import fr.gouv.vitamui.commons.api.identity.ServerIdentityAutoConfiguration;
 import fr.gouv.vitamui.commons.api.utils.CasJsonWrapper;
 import fr.gouv.vitamui.commons.rest.client.ExternalHttpContext;
 import fr.gouv.vitamui.commons.security.client.dto.AuthUserDto;
+import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
 import fr.gouv.vitamui.iam.external.client.CasExternalRestClient;
 import org.apereo.cas.authentication.SurrogateUsernamePasswordCredential;
 import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
@@ -78,7 +80,8 @@ public final class UserPrincipalResolverTest extends BaseWebflowActionTest {
         val utils = new Utils(null, 0, null, null);
         principalFactory = new DefaultPrincipalFactory();
         sessionStore = mock(SessionStore.class);
-        resolver = new UserPrincipalResolver(principalFactory, casExternalRestClient, utils, sessionStore);
+        resolver = new UserPrincipalResolver(principalFactory, casExternalRestClient, utils, sessionStore,
+            mock(IdentityProviderHelper.class), mock(ProvidersService.class));
     }
 
     @Test
-- 
GitLab