diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml index fdec4a7144d172a7f14622afd10eceaf30dcaea5..313966d678032c0b4093cfff653cd03b69921d36 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 74891cae4cd3cf7f4dd60609310fe412d78e567a..61763f5d8b21ebf9c9580e0c35e5f38f0a0ad08a 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 4a9ea04f1bcaf33be599f6a637a77220a7b2fef3..59ebda7cdbbe3f0c344a92bf9cb491c263a24d2f 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 0aa50da217d4e5f49bcab6b12c121adc1f41b4da..2d164516ffc7f19020db4b7d661aaa9d2f53352a 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 ee63213d9952d104063b890bb9d1ed2559f25628..cab87d9c917e12a88563931bda52b71906c210a9 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 e6ac7a15cbc2b987979256bc8f5a2e9bfdc1bbcb..81992b72d3a5263c06ff11ad4035b0f2783f7970 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