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