diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/saml/profile/IamSSOProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/saml/profile/IamSSOProfile.java index 289a3fdf6f..37545848c2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/saml/profile/IamSSOProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/saml/profile/IamSSOProfile.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.authn.saml.profile; -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -37,8 +36,16 @@ import org.springframework.security.saml.websso.WebSSOProfileImpl; import org.springframework.security.saml.websso.WebSSOProfileOptions; +import it.infn.mw.iam.config.saml.IamSamlProperties.AuthnContextProperties; + public class IamSSOProfile extends WebSSOProfileImpl { + private final AuthnContextProperties authnContextProperties; + + public IamSSOProfile(AuthnContextProperties authnContextProperties) { + this.authnContextProperties = authnContextProperties; + } + private void spidNameIDPolicy(AuthnRequest request) { @SuppressWarnings("unchecked") SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory @@ -75,34 +82,28 @@ private SAMLObjectBuilder getBuilder(QName elementName return (SAMLObjectBuilder) builderFactory.getBuilder(elementName); } - private void addRefedsAuthnContexts(AuthnRequest request) { - RequestedAuthnContext requestedAuthnContext = request.getRequestedAuthnContext(); - SAMLObjectBuilder builder = - getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME); - if (requestedAuthnContext == null) { - requestedAuthnContext = builder.buildObject(); - request.setRequestedAuthnContext(requestedAuthnContext); - } + private void configureAuthnContext(AuthnRequest request) { - List requiredClassRefs = - Arrays.asList("https://refeds.org/profile/mfa", "https://refeds.org/profile/sfa", - "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"); + if (!authnContextProperties.isEnabled()) { + // Actively clear any AuthnContext the parent class may have already set. + // This ensures NO RequestedAuthnContext is sent letting the IdP decide. + request.setRequestedAuthnContext(null); + return; + } - Set existingRefs = requestedAuthnContext.getAuthnContextClassRefs() - .stream() - .map(AuthnContextClassRef::getAuthnContextClassRef) - .collect(Collectors.toSet()); + // Clear and replace with the configured list (admin-defined or defaults) + SAMLObjectBuilder builder = + getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME); + RequestedAuthnContext requestedAuthnContext = builder.buildObject(); + request.setRequestedAuthnContext(requestedAuthnContext); SAMLObjectBuilder contextRefBuilder = getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); - for (String ref : requiredClassRefs) { - if (!existingRefs.contains(ref)) { - AuthnContextClassRef classRef = contextRefBuilder.buildObject(); - classRef.setAuthnContextClassRef(ref); - requestedAuthnContext.getAuthnContextClassRefs().add(classRef); - } + for (String ref : authnContextProperties.getClassRefs()) { + AuthnContextClassRef classRef = contextRefBuilder.buildObject(); + classRef.setAuthnContextClassRef(ref); + requestedAuthnContext.getAuthnContextClassRefs().add(classRef); } } @@ -114,7 +115,7 @@ protected AuthnRequest getAuthnRequest(SAMLMessageContext context, WebSSOProfile AuthnRequest request = super.getAuthnRequest(context, options, assertionConsumer, bindingService); - addRefedsAuthnContexts(request); + configureAuthnContext(request); if (options instanceof IamSSOProfileOptions) { IamSSOProfileOptions ssoOptions = (IamSSOProfileOptions) options; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/IamSamlProperties.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/IamSamlProperties.java index f3812354c3..882721bf5d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/IamSamlProperties.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/IamSamlProperties.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.config.saml; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -195,6 +196,34 @@ public String mode() { } } + public static class AuthnContextProperties { + + private boolean enabled = true; + + private List classRefs = Arrays.asList( + "https://refeds.org/profile/mfa", + "https://refeds.org/profile/sfa", + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" + ); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getClassRefs() { + return classRefs; + } + + public void setClassRefs(List classRefs) { + this.classRefs = classRefs; + } + } + private String entityId; private String keystore; @@ -243,6 +272,8 @@ public String mode() { private long httpClientSocketTimeoutSecs; + private AuthnContextProperties authnContext = new AuthnContextProperties(); + public List getIdpMetadata() { return idpMetadata; } @@ -427,4 +458,12 @@ public long getHttpClientSocketTimeoutSecs() { public void setHttpClientSocketTimeoutSecs(long httpClientSocketTimeoutSecs) { this.httpClientSocketTimeoutSecs = httpClientSocketTimeoutSecs; } + + public AuthnContextProperties getAuthnContext() { + return authnContext; + } + + public void setAuthnContext(AuthnContextProperties authnContext) { + this.authnContext = authnContext; + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/SamlConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/SamlConfig.java index 866719efd5..595fee9794 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/SamlConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/saml/SamlConfig.java @@ -301,7 +301,7 @@ SamlUserIdentifierResolver resolver() { @Bean @Profile("saml") WebSSOProfile webSSOprofile() { - return new IamSSOProfile(); + return new IamSSOProfile(samlProperties.getAuthnContext()); } diff --git a/iam-login-service/src/main/resources/application-saml.yml b/iam-login-service/src/main/resources/application-saml.yml index 813905f173..23b6da560f 100644 --- a/iam-login-service/src/main/resources/application-saml.yml +++ b/iam-login-service/src/main/resources/application-saml.yml @@ -42,4 +42,13 @@ saml: metadata-url: ${IAM_SAML_IDP_METADATA:classpath:/saml/idp-metadata.xml} require-valid-signature: ${IAM_SAML_METADATA_REQUIRE_VALID_SIGNATURE:false} require-sirtfi: ${IAM_SAML_METADATA_REQUIRE_SIRTFI:false} - require-rs: ${IAM_SAML_METADATA_REQUIRE_RS:false} \ No newline at end of file + require-rs: ${IAM_SAML_METADATA_REQUIRE_RS:false} + + authn-context: + enabled: ${IAM_SAML_AUTHN_CONTEXT_ENABLED:true} + # Set enabled to false to send no AuthnContext at all (IdP determines the appropriate authentication assurance). + # To customise the AuthnContextClassRef values, add a class-refs list + # at the same indentation level as 'enabled' above. For example: + # class-refs: + # - urn:federation:authentication:windows + # - urn:oasis:names:tc:SAML:2.0:ac:classes:Unspecified