From acf81628ef3648f9f793c8735be13c4b13348686 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Fri, 3 Nov 2023 15:22:13 +0100 Subject: [PATCH 1/9] First step in SAML migration - breaks everything --- myconext-server/pom.xml | 47 +++++++++++++++++++++++------------------ pom.xml | 11 ++++++++++ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/myconext-server/pom.xml b/myconext-server/pom.xml index 5522ba56..0ce8ebd3 100644 --- a/myconext-server/pom.xml +++ b/myconext-server/pom.xml @@ -45,10 +45,15 @@ org.springframework.boot spring-boot-starter-web + + + + + - org.springframework.security.extensions - spring-security-saml2-core - 2.0.0.M31 + org.openconext + saml-idp + 0.0.1-SNAPSHOT org.springframework.boot @@ -226,24 +231,24 @@ org.springframework.boot spring-boot-maven-plugin - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + pl.project13.maven git-commit-id-plugin diff --git a/pom.xml b/pom.xml index 648300e9..5b7d92b0 100644 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,17 @@ OpenConext public snapshot repository dav:https://build.openconext.org/repository/public/snapshots + + + true + + + true + + shibboleth + shibboleth + https://build.shibboleth.net/maven/releases/ + true From dba2041977572a1b7fc840b50acc86d5b771ae55 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Sat, 4 Nov 2023 16:54:41 +0100 Subject: [PATCH 2/9] Added TODO for saml migration --- .../src/main/java/myconext/security/SecurityConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index 32be2c76..adbfcfe9 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -84,6 +84,7 @@ private List commaSeparatedToList(String spEntityId) { @Override protected void configure(HttpSecurity http) throws Exception { + //TODO add GuestIdpAuthenticationRequestFilter before AbstractPreAuthenticatedProcessingFilter super.configure(http); String prefix = getPrefix(); From cf70dfa73434db44be3aea48581a0aea11ec7aef Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Sat, 4 Nov 2023 16:56:33 +0100 Subject: [PATCH 3/9] Added TODO for saml migration --- .../security/GuestIdpAuthenticationRequestFilter.java | 5 +++++ .../main/java/myconext/security/SecurityConfiguration.java | 2 ++ 2 files changed, 7 insertions(+) diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index b85dc8d6..645d8e21 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -114,6 +114,11 @@ public GuestIdpAuthenticationRequestFilter(SamlProviderProvisioning commaSeparatedToList(String spEntityId) { @Override protected void configure(HttpSecurity http) throws Exception { //TODO add GuestIdpAuthenticationRequestFilter before AbstractPreAuthenticatedProcessingFilter + + super.configure(http); String prefix = getPrefix(); From e8668696d8118eeaacece7adad29deb3299b783d Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 6 Nov 2023 10:44:09 +0100 Subject: [PATCH 4/9] WIP for SAML migration --- .../main/java/myconext/config/BeanConfig.java | 2 +- .../security/SecurityConfiguration.java | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/myconext-server/src/main/java/myconext/config/BeanConfig.java b/myconext-server/src/main/java/myconext/config/BeanConfig.java index 8c51f9f0..fc7707a9 100644 --- a/myconext-server/src/main/java/myconext/config/BeanConfig.java +++ b/myconext-server/src/main/java/myconext/config/BeanConfig.java @@ -18,7 +18,7 @@ import javax.servlet.Filter; @Configuration -public class BeanConfig extends SamlIdentityProviderServerBeanConfiguration { +public class BeanConfig { private final String redirectUrl; private final AuthenticationRequestRepository authenticationRequestRepository; diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index 4d10541e..a6ccff50 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -2,8 +2,12 @@ import myconext.config.BeanConfig; import myconext.crypto.KeyGenerator; +import myconext.geo.GeoLocation; +import myconext.mail.MailBox; import myconext.manage.ServiceProviderResolver; import myconext.model.ServiceProvider; +import myconext.repository.AuthenticationRequestRepository; +import myconext.repository.UserLoginRepository; import myconext.repository.UserRepository; import myconext.shibboleth.ShibbolethPreAuthenticatedProcessingFilter; import myconext.shibboleth.ShibbolethUserDetailService; @@ -52,21 +56,33 @@ public class SecurityConfiguration { @Configuration @Order(1) - public static class SamlSecurity extends SamlIdentityProviderSecurityConfiguration { + public static class SamlSecurity { private final Resource privateKeyPath; private final Resource certificatePath; private final List serviceProviders = new ArrayList<>(); + private final String redirectUrl; + private final AuthenticationRequestRepository authenticationRequestRepository; + private final UserRepository userRepository; + private final UserLoginRepository userLoginRepository; + private final int rememberMeMaxAge; + private final int nudgeAppDays; + private final int rememberMeQuestionAskedDays; + private final long expiryNonValidatedDurationDays; + private final long ssoMFADurationSeconds; + private final String mobileAppROEntityId; + private final boolean secureCookie; + private final String magicLinkUrl; + private final MailBox mailBox; + private final ServiceProviderResolver serviceProviderResolver; + private final GeoLocation geoLocation; + private final boolean featureDefaultRememberMe; private final String idpEntityId; - private final BeanConfig beanConfig; - public SamlSecurity(BeanConfig beanConfig, - @Value("${private_key_path}") Resource privateKeyPath, + public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, @Value("${certificate_path}") Resource certificatePath, @Value("${idp_entity_id}") String idpEntityId, @Value("${sp_entity_id}") String spEntityId, @Value("${sp_entity_metadata_url}") String spMetaDataUrl) { - super("/saml/guest-idp/", beanConfig); - this.beanConfig = beanConfig; this.privateKeyPath = privateKeyPath; this.certificatePath = certificatePath; this.idpEntityId = idpEntityId; From 18371e2314da54cb07e56edd2f7a1832dbe583af Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 6 Nov 2023 15:07:45 +0100 Subject: [PATCH 5/9] WIP for SAML migration --- .../security/GuestIdentityProviderDsl.java | 46 ---------------- .../GuestIdpAuthenticationRequestFilter.java | 40 +++++++------- .../security/SecurityConfiguration.java | 55 +++++++++++++------ 3 files changed, 56 insertions(+), 85 deletions(-) delete mode 100644 myconext-server/src/main/java/myconext/security/GuestIdentityProviderDsl.java diff --git a/myconext-server/src/main/java/myconext/security/GuestIdentityProviderDsl.java b/myconext-server/src/main/java/myconext/security/GuestIdentityProviderDsl.java deleted file mode 100644 index 5a96245e..00000000 --- a/myconext-server/src/main/java/myconext/security/GuestIdentityProviderDsl.java +++ /dev/null @@ -1,46 +0,0 @@ -package myconext.security; - -import myconext.config.BeanConfig; -import org.springframework.context.ApplicationContext; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.saml.provider.SamlServerConfiguration; -import org.springframework.security.saml.provider.identity.config.SamlIdentityProviderSecurityDsl; -import org.springframework.security.web.context.SecurityContextPersistenceFilter; - -import javax.servlet.Filter; - -public class GuestIdentityProviderDsl extends SamlIdentityProviderSecurityDsl { - - private final BeanConfig beanConfig; - - GuestIdentityProviderDsl(BeanConfig beanConfig) { - this.beanConfig = beanConfig; - } - - @Override - public void configure(HttpSecurity http) throws Exception { - - super.configure(http); - - ApplicationContext context = http.getSharedObject(ApplicationContext.class); - - SamlServerConfiguration serverConfig = context.getBean("idpSamlServerConfiguration", SamlServerConfiguration.class); - - Filter samlConfigurationFilter = beanConfig.samlConfigurationFilter(serverConfig); - Filter metadataFilter = beanConfig.idpMetadataFilter(); - Filter idpAuthnRequestFilter = beanConfig.idpAuthnRequestFilter(); - http - .addFilterAfter( - samlConfigurationFilter, - SecurityContextPersistenceFilter.class - ) - .addFilterAfter( - metadataFilter, - samlConfigurationFilter.getClass() - ) - .addFilterAfter( - idpAuthnRequestFilter, - metadataFilter.getClass() - ); - } -} diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 645d8e21..3c8e0ba6 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -3,7 +3,6 @@ import myconext.exceptions.UserNotFoundException; import myconext.geo.GeoLocation; import myconext.mail.MailBox; -import myconext.manage.ServiceProviderHolder; import myconext.manage.ServiceProviderResolver; import myconext.model.*; import myconext.repository.AuthenticationRequestRepository; @@ -11,7 +10,7 @@ import myconext.repository.UserRepository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Value; +import org.opensaml.saml.saml2.core.AuthnRequest; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -30,9 +29,13 @@ import org.springframework.security.saml.saml2.metadata.Endpoint; import org.springframework.security.saml.saml2.metadata.NameId; import org.springframework.security.saml.saml2.metadata.ServiceProviderMetadata; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.HtmlUtils; +import saml.DefaultSAMLIdPService; +import saml.model.SAMLConfiguration; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -60,7 +63,7 @@ import static org.springframework.util.StringUtils.hasText; @SuppressWarnings("unchecked") -public class GuestIdpAuthenticationRequestFilter extends IdpAuthenticationRequestFilter implements ServiceProviderHolder { +public class GuestIdpAuthenticationRequestFilter extends OncePerRequestFilter { public static final String GUEST_IDP_REMEMBER_ME_COOKIE_NAME = "guest-idp-remember-me"; public static final String TRACKING_DEVICE_COOKIE_NAME = "TRACKING_DEVICE"; @@ -72,9 +75,9 @@ public class GuestIdpAuthenticationRequestFilter extends IdpAuthenticationReques private static final Log LOG = LogFactory.getLog(GuestIdpAuthenticationRequestFilter.class); public static final String ROLE_MFA = "ROLE_MFA"; - private final SamlRequestMatcher ssoSamlRequestMatcher; - private final SamlRequestMatcher magicSamlRequestMatcher; - private final SamlRequestMatcher continueAfterloginSamlRequestMatcher; + private final AntPathRequestMatcher ssoSamlRequestMatcher; + private final AntPathRequestMatcher magicSamlRequestMatcher; + private final AntPathRequestMatcher continueAfterloginSamlRequestMatcher; private final String redirectUrl; private final AuthenticationRequestRepository authenticationRequestRepository; private final UserRepository userRepository; @@ -94,10 +97,9 @@ public class GuestIdpAuthenticationRequestFilter extends IdpAuthenticationReques private final long ssoMFADurationSeconds; private final String mobileAppROEntityId; private final boolean featureDefaultRememberMe; + private final DefaultSAMLIdPService samlIdpService; - public GuestIdpAuthenticationRequestFilter(SamlProviderProvisioning provisioning, - SamlMessageStore assertionStore, - String redirectUrl, + public GuestIdpAuthenticationRequestFilter(String redirectUrl, ServiceProviderResolver serviceProviderResolver, AuthenticationRequestRepository authenticationRequestRepository, UserRepository userRepository, @@ -112,16 +114,11 @@ public GuestIdpAuthenticationRequestFilter(SamlProviderProvisioning serviceProviders = new ArrayList<>(); - private final String redirectUrl; - private final AuthenticationRequestRepository authenticationRequestRepository; - private final UserRepository userRepository; - private final UserLoginRepository userLoginRepository; - private final int rememberMeMaxAge; - private final int nudgeAppDays; - private final int rememberMeQuestionAskedDays; - private final long expiryNonValidatedDurationDays; - private final long ssoMFADurationSeconds; - private final String mobileAppROEntityId; - private final boolean secureCookie; - private final String magicLinkUrl; - private final MailBox mailBox; - private final ServiceProviderResolver serviceProviderResolver; - private final GeoLocation geoLocation; - private final boolean featureDefaultRememberMe; private final String idpEntityId; public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, @Value("${certificate_path}") Resource certificatePath, @Value("${idp_entity_id}") String idpEntityId, @Value("${sp_entity_id}") String spEntityId, - @Value("${sp_entity_metadata_url}") String spMetaDataUrl) { + @Value("${sp_entity_metadata_url}") String spMetaDataUrl, + @Value("${saml_metadata_base_path}") String samlMetadataBasePath, + @Value("${idp_redirect_url}") String redirectUrl, + @Value("${remember_me_max_age_seconds}") int rememberMeMaxAge, + @Value("${nudge_eduid_app_days}") int nudgeAppDays, + @Value("${remember_me_question_asked_days}") int rememberMeQuestionAskedDays, + @Value("${secure_cookie}") boolean secureCookie, + @Value("${email.magic-link-url}") String magicLinkUrl, + @Value("${account_linking_context_class_ref.linked_institution}") String linkedInstitution, + @Value("${account_linking_context_class_ref.validate_names}") String validateNames, + @Value("${account_linking_context_class_ref.affiliation_student}") String affiliationStudent, + @Value("${account_linking_context_class_ref.profile_mfa}") String profileMfa, + @Value("${linked_accounts.expiry-duration-days-non-validated}") long expiryNonValidatedDurationDays, + @Value("${sso_mfa_duration_seconds}") long ssoMFADurationSeconds, + @Value("${mobile_app_rp_entity_id}") String mobileAppROEntityId, + @Value("${feature.default_remember_me}") boolean featureDefaultRememberMe, + AuthenticationRequestRepository authenticationRequestRepository, + UserRepository userRepository, + UserLoginRepository userLoginRepository, + GeoLocation geoLocation, + MailBox mailBox, + ServiceProviderResolver serviceProviderResolver) { + List serviceProviders = new ArrayList<>(); + this.privateKeyPath = privateKeyPath; this.certificatePath = certificatePath; this.idpEntityId = idpEntityId; @@ -100,6 +107,18 @@ private List commaSeparatedToList(String spEntityId) { @Override protected void configure(HttpSecurity http) throws Exception { + http + .requestMatchers() + .antMatchers("/saml/guest-idp/**") + .and() + .csrf() + .disable() + .addFilterBefore(this.guestIdpAuthenticationRequestFilter, + AbstractPreAuthenticatedProcessingFilter.class + ) + .authorizeRequests() + .antMatchers("/**").hasRole("GUEST"); + //TODO add GuestIdpAuthenticationRequestFilter before AbstractPreAuthenticatedProcessingFilter From a92cf51685011bced66d8970fc13a6ae8894c251 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 6 Nov 2023 16:18:52 +0100 Subject: [PATCH 6/9] WIP for SAML migration --- .../main/java/myconext/config/BeanConfig.java | 122 ----------- .../main/java/myconext/config/NoopFilter.java | 12 -- .../ImmutableSamlConfigurationRepository.java | 33 --- .../GuestIdpAuthenticationRequestFilter.java | 189 ++++++++---------- .../security/SecurityConfiguration.java | 101 ++++------ .../java/myconext/session/SessionConfig.java | 5 - ...estIdpAuthenticationRequestFilterTest.java | 43 ++-- 7 files changed, 138 insertions(+), 367 deletions(-) delete mode 100644 myconext-server/src/main/java/myconext/config/BeanConfig.java delete mode 100644 myconext-server/src/main/java/myconext/config/NoopFilter.java delete mode 100644 myconext-server/src/main/java/myconext/saml/ImmutableSamlConfigurationRepository.java diff --git a/myconext-server/src/main/java/myconext/config/BeanConfig.java b/myconext-server/src/main/java/myconext/config/BeanConfig.java deleted file mode 100644 index fc7707a9..00000000 --- a/myconext-server/src/main/java/myconext/config/BeanConfig.java +++ /dev/null @@ -1,122 +0,0 @@ -package myconext.config; - -import myconext.geo.GeoLocation; -import myconext.mail.MailBox; -import myconext.manage.ServiceProviderResolver; -import myconext.repository.AuthenticationRequestRepository; -import myconext.repository.UserLoginRepository; -import myconext.repository.UserRepository; -import myconext.saml.ImmutableSamlConfigurationRepository; -import myconext.security.ACR; -import myconext.security.GuestIdpAuthenticationRequestFilter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.saml.provider.SamlServerConfiguration; -import org.springframework.security.saml.provider.config.SamlConfigurationRepository; -import org.springframework.security.saml.provider.identity.config.SamlIdentityProviderServerBeanConfiguration; - -import javax.servlet.Filter; - -@Configuration -public class BeanConfig { - - private final String redirectUrl; - private final AuthenticationRequestRepository authenticationRequestRepository; - private final UserRepository userRepository; - private final UserLoginRepository userLoginRepository; - private final int rememberMeMaxAge; - private final int nudgeAppDays; - private final int rememberMeQuestionAskedDays; - private final long expiryNonValidatedDurationDays; - private final long ssoMFADurationSeconds; - private final String mobileAppROEntityId; - private final boolean secureCookie; - private final String magicLinkUrl; - private final MailBox mailBox; - private final ServiceProviderResolver serviceProviderResolver; - private final GeoLocation geoLocation; - private final boolean featureDefaultRememberMe; - - public BeanConfig(@Value("${saml_metadata_base_path}") String samlMetadataBasePath, - @Value("${idp_redirect_url}") String redirectUrl, - @Value("${remember_me_max_age_seconds}") int rememberMeMaxAge, - @Value("${nudge_eduid_app_days}") int nudgeAppDays, - @Value("${remember_me_question_asked_days}") int rememberMeQuestionAskedDays, - @Value("${secure_cookie}") boolean secureCookie, - @Value("${email.magic-link-url}") String magicLinkUrl, - @Value("${account_linking_context_class_ref.linked_institution}") String linkedInstitution, - @Value("${account_linking_context_class_ref.validate_names}") String validateNames, - @Value("${account_linking_context_class_ref.affiliation_student}") String affiliationStudent, - @Value("${account_linking_context_class_ref.profile_mfa}") String profileMfa, - @Value("${linked_accounts.expiry-duration-days-non-validated}") long expiryNonValidatedDurationDays, - @Value("${sso_mfa_duration_seconds}") long ssoMFADurationSeconds, - @Value("${mobile_app_rp_entity_id}") String mobileAppROEntityId, - @Value("${feature.default_remember_me}") boolean featureDefaultRememberMe, - AuthenticationRequestRepository authenticationRequestRepository, - UserRepository userRepository, - UserLoginRepository userLoginRepository, - GeoLocation geoLocation, - MailBox mailBox, - ServiceProviderResolver serviceProviderResolver) { - this.immutableSamlConfigurationRepository = new ImmutableSamlConfigurationRepository(samlMetadataBasePath); - this.redirectUrl = redirectUrl; - this.rememberMeMaxAge = rememberMeMaxAge; - this.nudgeAppDays = nudgeAppDays; - this.rememberMeQuestionAskedDays = rememberMeQuestionAskedDays; - this.secureCookie = secureCookie; - this.expiryNonValidatedDurationDays = expiryNonValidatedDurationDays; - this.ssoMFADurationSeconds = ssoMFADurationSeconds; - this.mobileAppROEntityId = mobileAppROEntityId; - this.featureDefaultRememberMe= featureDefaultRememberMe; - this.authenticationRequestRepository = authenticationRequestRepository; - this.userRepository = userRepository; - this.userLoginRepository = userLoginRepository; - this.geoLocation = geoLocation; - this.magicLinkUrl = magicLinkUrl; - this.mailBox = mailBox; - this.serviceProviderResolver = serviceProviderResolver; - - ACR.initialize(linkedInstitution, validateNames, affiliationStudent, profileMfa); - } - - private final ImmutableSamlConfigurationRepository immutableSamlConfigurationRepository; - - @Override - protected SamlServerConfiguration getDefaultHostSamlServerConfiguration() { - return new SamlServerConfiguration(); - } - - @Override - public Filter idpAuthnRequestFilter() { - return new GuestIdpAuthenticationRequestFilter( - getSamlProvisioning(), - samlAssertionStore(), - redirectUrl, - serviceProviderResolver, - authenticationRequestRepository, - userRepository, - userLoginRepository, - geoLocation, - rememberMeMaxAge, - nudgeAppDays, - rememberMeQuestionAskedDays, - secureCookie, - magicLinkUrl, - mailBox, - expiryNonValidatedDurationDays, - ssoMFADurationSeconds, - mobileAppROEntityId, - featureDefaultRememberMe); - } - - public Filter samlConfigurationFilter(SamlServerConfiguration serverConfig) { - this.immutableSamlConfigurationRepository.setConfiguration(serverConfig); - return new NoopFilter(); - } - - @Override - public SamlConfigurationRepository samlConfigurationRepository() { - return immutableSamlConfigurationRepository; - } - -} diff --git a/myconext-server/src/main/java/myconext/config/NoopFilter.java b/myconext-server/src/main/java/myconext/config/NoopFilter.java deleted file mode 100644 index 6aaf5ae5..00000000 --- a/myconext-server/src/main/java/myconext/config/NoopFilter.java +++ /dev/null @@ -1,12 +0,0 @@ -package myconext.config; - -import javax.servlet.*; -import java.io.IOException; - -public class NoopFilter extends GenericFilter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - chain.doFilter(request, response); - } -} diff --git a/myconext-server/src/main/java/myconext/saml/ImmutableSamlConfigurationRepository.java b/myconext-server/src/main/java/myconext/saml/ImmutableSamlConfigurationRepository.java deleted file mode 100644 index 2819cd36..00000000 --- a/myconext-server/src/main/java/myconext/saml/ImmutableSamlConfigurationRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package myconext.saml; - -import org.springframework.security.saml.provider.SamlServerConfiguration; -import org.springframework.security.saml.provider.config.LocalProviderConfiguration; -import org.springframework.security.saml.provider.config.SamlConfigurationRepository; - -import static java.util.Arrays.asList; -import static org.springframework.util.StringUtils.hasText; - -public class ImmutableSamlConfigurationRepository implements SamlConfigurationRepository { - - private SamlServerConfiguration configuration; - private String basePath; - - public ImmutableSamlConfigurationRepository(String basePath) { - this.basePath = basePath; - } - - @Override - public SamlServerConfiguration getServerConfiguration() { - return configuration; - } - - public void setConfiguration(SamlServerConfiguration configuration) { - this.configuration = configuration; - for (LocalProviderConfiguration config : asList(configuration.getIdentityProvider(), configuration.getServiceProvider())) { - if (config != null && !hasText(config.getBasePath())) { - config.setBasePath(basePath); - } - } - } -} - diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 3c8e0ba6..807143c9 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -1,8 +1,11 @@ package myconext.security; +import lombok.NoArgsConstructor; import myconext.exceptions.UserNotFoundException; import myconext.geo.GeoLocation; import myconext.mail.MailBox; +import myconext.manage.MockServiceProviderResolver; +import myconext.manage.ServiceProviderHolder; import myconext.manage.ServiceProviderResolver; import myconext.model.*; import myconext.repository.AuthenticationRequestRepository; @@ -10,32 +13,22 @@ import myconext.repository.UserRepository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.saml.saml2.core.*; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.saml.SamlMessageStore; -import org.springframework.security.saml.SamlRequestMatcher; -import org.springframework.security.saml.provider.identity.IdentityProviderService; -import org.springframework.security.saml.provider.identity.IdpAuthenticationRequestFilter; -import org.springframework.security.saml.provider.provisioning.SamlProviderProvisioning; -import org.springframework.security.saml.saml2.attribute.Attribute; -import org.springframework.security.saml.saml2.attribute.AttributeNameFormat; -import org.springframework.security.saml.saml2.authentication.*; -import org.springframework.security.saml.saml2.metadata.Binding; -import org.springframework.security.saml.saml2.metadata.Endpoint; -import org.springframework.security.saml.saml2.metadata.NameId; -import org.springframework.security.saml.saml2.metadata.ServiceProviderMetadata; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.HtmlUtils; import saml.DefaultSAMLIdPService; +import saml.model.SAMLAttribute; import saml.model.SAMLConfiguration; +import saml.model.SAMLStatus; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -60,10 +53,10 @@ import static myconext.log.MDCContext.logLoginWithContext; import static myconext.log.MDCContext.logWithContext; import static myconext.security.CookieResolver.cookieByName; -import static org.springframework.util.StringUtils.hasText; @SuppressWarnings("unchecked") -public class GuestIdpAuthenticationRequestFilter extends OncePerRequestFilter { +@NoArgsConstructor(force = true) +public class GuestIdpAuthenticationRequestFilter extends OncePerRequestFilter implements ServiceProviderHolder { public static final String GUEST_IDP_REMEMBER_ME_COOKIE_NAME = "guest-idp-remember-me"; public static final String TRACKING_DEVICE_COOKIE_NAME = "TRACKING_DEVICE"; @@ -80,7 +73,7 @@ public class GuestIdpAuthenticationRequestFilter extends OncePerRequestFilter { private final AntPathRequestMatcher continueAfterloginSamlRequestMatcher; private final String redirectUrl; private final AuthenticationRequestRepository authenticationRequestRepository; - private final UserRepository userRepository; + private UserRepository userRepository; private final UserLoginRepository userLoginRepository; private final List accountLinkingContextClassReferences; private final GeoLocation geoLocation; @@ -89,7 +82,7 @@ public class GuestIdpAuthenticationRequestFilter extends OncePerRequestFilter { private final boolean secureCookie; private final String magicLinkUrl; private final MailBox mailBox; - private final ServiceProviderResolver serviceProviderResolver; + private ServiceProviderResolver serviceProviderResolver; private final ExecutorService executor; private final int nudgeAppDays; private final int rememberMeQuestionAskedDays; @@ -167,22 +160,19 @@ private void sso(HttpServletRequest request, HttpServletResponse response) throw return; } AuthnRequest authnRequest = this.samlIdpService.parseAuthnRequest(samlRequest, true, isDeflated(request)); - AuthenticationRequest authenticationRequest = - provider.fromXml(samlRequest, true, isDeflated(request), AuthenticationRequest.class); - provider.validate(authenticationRequest); - String requesterEntityId = requesterId(authenticationRequest); - String issuer = authenticationRequest.getIssuer().getValue(); + String requesterEntityId = requesterId(authnRequest); + String issuer = authnRequest.getIssuer().getValue(); - List authenticationContextClassReferenceValues = getAuthenticationContextClassReferenceValues(authenticationRequest); + List authenticationContextClassReferenceValues = getAuthenticationContextClassReferenceValues(authnRequest); boolean accountLinkingRequired = this.accountLinkingContextClassReferences.stream().anyMatch(authenticationContextClassReferenceValues::contains); boolean mfaProfileRequired = authenticationContextClassReferenceValues.contains(ACR.PROFILE_MFA); SamlAuthenticationRequest samlAuthenticationRequest = new SamlAuthenticationRequest( - authenticationRequest.getId(), + authnRequest.getID(), issuer, - authenticationRequest.getAssertionConsumerService().getLocation(), + authnRequest.getAssertionConsumerServiceURL(), relayState, StringUtils.hasText(requesterEntityId) ? requesterEntityId : "", accountLinkingRequired, @@ -203,7 +193,7 @@ private void sso(HttpServletRequest request, HttpServletResponse response) throw samlAuthenticationRequest.setServiceName(serviceName); samlAuthenticationRequest = authenticationRequestRepository.save(samlAuthenticationRequest); - if (previousAuthenticatedUser != null && !authenticationRequest.isForceAuth()) { + if (previousAuthenticatedUser != null && !authnRequest.isForceAuthn()) { boolean applySsoMfa = isApplySsoMfa(); if ((!applySsoMfa && mfaProfileRequired) || (accountLinkingRequired && !isUserVerifiedByInstitution(previousAuthenticatedUser, authenticationContextClassReferenceValues))) { @@ -228,9 +218,7 @@ private void sso(HttpServletRequest request, HttpServletResponse response) throw + "?explanation=" + explanation + mfa; response.sendRedirect(location); } else { - ServiceProviderMetadata serviceProviderMetadata = provider.getRemoteProvider(samlAuthenticationRequest.getIssuer()); - sendAssertion(request, response, samlAuthenticationRequest, previousAuthenticatedUser, - provider, serviceProviderMetadata, authenticationRequest); + sendAssertion(request, response, samlAuthenticationRequest, previousAuthenticatedUser); } } else { addBrowserIdentificationCookie(response); @@ -240,7 +228,7 @@ private void sso(HttpServletRequest request, HttpServletResponse response) throw String stepUp = accountLinkingRequired ? "&stepup=true" : ""; String mfa = mfaProfileRequired ? "&mfa=true" : ""; - String preferMagicLink = this.nudgeMagicLink(authenticationRequest) ? "&magicLink=true" : ""; + String preferMagicLink = this.nudgeMagicLink(authnRequest) ? "&magicLink=true" : ""; String separator = (accountLinkingRequired || mfaProfileRequired || StringUtils.hasText(preferMagicLink)) ? "?n=1" : ""; String path = optionalCookie.map(c -> "/request/").orElse("/login/"); String location = this.redirectUrl + path + samlAuthenticationRequest.getId() + @@ -297,13 +285,13 @@ public static boolean hasRequiredStudentAffiliation(List affiliations) { .anyMatch(affiliation -> affiliation.startsWith("student@")); } - private List getAuthenticationContextClassReferenceValues(AuthenticationRequest authenticationRequest) { - List authenticationContextClassReferences = authenticationRequest.getAuthenticationContextClassReferences(); - if (authenticationContextClassReferences == null) { + private List getAuthenticationContextClassReferenceValues(AuthnRequest authenticationRequest) { + List authnContextClassRefs = authenticationRequest.getRequestedAuthnContext().getAuthnContextClassRefs(); + if (authnContextClassRefs == null) { return Collections.emptyList(); } - return authenticationContextClassReferences.stream() - .map(AuthenticationContextClassReference::getValue) + return authnContextClassRefs.stream() + .map(XSURI::getURI) .collect(toList()); } @@ -333,19 +321,19 @@ private boolean isDeflated(HttpServletRequest request) { return HttpMethod.GET.name().equalsIgnoreCase(request.getMethod()); } - private String requesterId(AuthenticationRequest authenticationRequest) { + private String requesterId(AuthnRequest authenticationRequest) { Issuer issuer = authenticationRequest.getIssuer(); String issuerValue = issuer != null ? issuer.getValue() : ""; Scoping scoping = authenticationRequest.getScoping(); - List requesterIds = scoping != null ? scoping.getRequesterIds() : null; - return CollectionUtils.isEmpty(requesterIds) ? issuerValue : requesterIds.get(0); + List requesterIDS = scoping != null ? scoping.getRequesterIDs() : null; + return CollectionUtils.isEmpty(requesterIDS) ? issuerValue : requesterIDS.get(0).getURI(); } - private boolean nudgeMagicLink(AuthenticationRequest authenticationRequest) { + private boolean nudgeMagicLink(AuthnRequest authenticationRequest) { Scoping scoping = authenticationRequest.getScoping(); - List requesterIds = scoping != null ? scoping.getRequesterIds() : null; - return !CollectionUtils.isEmpty(requesterIds) && requesterIds.stream() - .anyMatch(this.mobileAppROEntityId::equalsIgnoreCase); + List requesterIDS = scoping != null ? scoping.getRequesterIDs() : null; + return !CollectionUtils.isEmpty(requesterIDS) && requesterIDS.stream() + .anyMatch(requesterID -> this.mobileAppROEntityId.equalsIgnoreCase(requesterID.getURI())); } private void magic(HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -529,7 +517,10 @@ private void continueAfterLogin(HttpServletRequest request, HttpServletResponse doSendAssertion(request, response, samlAuthenticationRequest, user); } - private void doSendAssertion(HttpServletRequest request, HttpServletResponse response, SamlAuthenticationRequest samlAuthenticationRequest, User user) { + private void doSendAssertion(HttpServletRequest request, + HttpServletResponse response, + SamlAuthenticationRequest samlAuthenticationRequest, + User user) { //ensure the magic link can't be used twice samlAuthenticationRequest.setHash(null); @@ -537,13 +528,6 @@ private void doSendAssertion(HttpServletRequest request, HttpServletResponse res authenticationRequestRepository.save(samlAuthenticationRequest); - IdentityProviderService provider = getProvisioning().getHostedProvider(); - ServiceProviderMetadata serviceProviderMetadata = provider.getRemoteProvider(samlAuthenticationRequest.getIssuer()); - - AuthenticationRequest authenticationRequest = new AuthenticationRequest(); - authenticationRequest.setId(samlAuthenticationRequest.getRequestId()); - authenticationRequest.setAssertionConsumerService(new Endpoint().setLocation(samlAuthenticationRequest.getConsumerAssertionServiceURL())); - //We support SSO for MFA, we must mark the authentication with a timestamp and an extra role boolean mfaProfileRequired = samlAuthenticationRequest.isMfaProfileRequired(); Collection authorities = user.getAuthorities(); @@ -566,8 +550,7 @@ private void doSendAssertion(HttpServletRequest request, HttpServletResponse res logLoginWithContext(user, loginMethod, true, LOG, "Successfully logged in with " + loginMethod); - sendAssertion(request, response, samlAuthenticationRequest, user, provider, - serviceProviderMetadata, authenticationRequest); + sendAssertion(request, response, samlAuthenticationRequest, user); } private void finishStepUp(SamlAuthenticationRequest samlAuthenticationRequest) { @@ -631,19 +614,20 @@ private void addTrackingCookie(HttpServletRequest request, HttpServletResponse r } } - private void sendAssertion(HttpServletRequest request, HttpServletResponse response, SamlAuthenticationRequest samlAuthenticationRequest, - User user, IdentityProviderService provider, ServiceProviderMetadata serviceProviderMetadata, - AuthenticationRequest authenticationRequest) { + private void sendAssertion(HttpServletRequest request, + HttpServletResponse response, + SamlAuthenticationRequest samlAuthenticationRequest, + User user) { String relayState = samlAuthenticationRequest.getRelayState(); String requesterEntityId = samlAuthenticationRequest.getRequesterEntityId(); - Assertion assertion = provider.assertion( - serviceProviderMetadata, authenticationRequest, user.getUid(), NameId.PERSISTENT); List authenticationContextClassReferences = samlAuthenticationRequest.getAuthenticationContextClassReferences(); - attributes(user, requesterEntityId).forEach(assertion::addAttribute); + List attributes = attributes(user, requesterEntityId); - Response samlResponse = provider.response(authenticationRequest, assertion, serviceProviderMetadata); boolean applySsoMfa = this.isApplySsoMfa(); + SAMLStatus samlStatus = SAMLStatus.SUCCESS; + String optionalMessage = null; + String authnContextClassRefValue = DefaultSAMLIdPService.authnContextClassRefPassword; if (samlAuthenticationRequest.isAccountLinkingRequired()) { boolean hasStudentAffiliation = hasRequiredStudentAffiliation(user.allEduPersonAffiliations()); @@ -653,57 +637,30 @@ private void sendAssertion(HttpServletRequest request, HttpServletResponse respo boolean missingValidName = authenticationContextClassReferences.contains(ACR.VALIDATE_NAMES) && !hasValidatedName(user); if (missingStudentAffiliation || missingValidName) { - String msg; if (missingValidName) { - msg = "The requesting service has indicated that the authenticated user is required to have a first_name and last_name." + + optionalMessage = "The requesting service has indicated that the authenticated user is required to have a first_name and last_name." + " Your institution has not provided those attributes."; } else { - msg = "The requesting service has indicated that the authenticated user is required to have an affiliation Student." + + optionalMessage = "The requesting service has indicated that the authenticated user is required to have an affiliation Student." + " Your institution has not provided this affiliation."; } - samlResponse.setStatus(new Status() - .setCode(StatusCode.NO_AUTH_CONTEXT) - .setMessage(msg) - .setDetail(msg)); + samlStatus = SAMLStatus.NO_AUTHN_CONTEXT; } else { - samlResponse.getAssertions().get(0).getAuthenticationStatements().get(0) - .getAuthenticationContext() - .setClassReference(AuthenticationContextClassReference - .fromUrn(ACR.selectACR(authenticationContextClassReferences, hasStudentAffiliation))); + authnContextClassRefValue = ACR.selectACR(authenticationContextClassReferences, hasStudentAffiliation); } } else if (samlAuthenticationRequest.isMfaProfileRequired()) { if (samlAuthenticationRequest.isTiqrFlow() || applySsoMfa) { - samlResponse.getAssertions().get(0).getAuthenticationStatements().get(0) - .getAuthenticationContext() - .setClassReference(AuthenticationContextClassReference - .fromUrn(ACR.selectACR(authenticationContextClassReferences, false))); + authnContextClassRefValue = ACR.selectACR(authenticationContextClassReferences, false); } else { - String msg = "The requesting service has indicated that a login with the eduID app is required to login."; - samlResponse.setStatus(new Status() - .setCode(StatusCode.NO_AUTH_CONTEXT) - .setMessage(msg) - .setDetail(msg)); + optionalMessage = "The requesting service has indicated that a login with the eduID app is required to login."; + samlStatus = SAMLStatus.NO_AUTHN_CONTEXT; } } else if (!applySsoMfa && !CollectionUtils.isEmpty(authenticationContextClassReferences)) { - String msg = String.format("The specified authentication context requirements '%s' cannot be met by the responder.", + optionalMessage = String.format("The specified authentication context requirements '%s' cannot be met by the responder.", String.join(", ", authenticationContextClassReferences)); - samlResponse.setStatus(new Status() - .setCode(StatusCode.NO_AUTH_CONTEXT) - .setMessage(msg) - .setDetail(msg)); - } - Endpoint acsUrl = provider.getPreferredEndpoint( - serviceProviderMetadata.getServiceProvider().getAssertionConsumerService(), - Binding.POST, - -1 - ); - String encoded = provider.toEncodedXml(samlResponse, false); - Map model = new HashMap<>(); - model.put("action", acsUrl.getLocation()); - model.put("SAMLResponse", encoded); - if (hasText(relayState)) { - model.put("RelayState", HtmlUtils.htmlEscape(relayState)); + samlStatus = SAMLStatus.NO_AUTHN_CONTEXT; } + Optional optionalCookie = cookieByName(request, BROWSER_SESSION_COOKIE_NAME); optionalCookie.ifPresent(cookie -> { cookie.setMaxAge(0); @@ -711,14 +668,32 @@ private void sendAssertion(HttpServletRequest request, HttpServletResponse respo }); //Tracking cookie for user new device discovery this.addTrackingCookie(request, response, user); - processHtml(request, response, getPostBindingTemplate(), model); + this.samlIdpService.sendResponse( + samlAuthenticationRequest.getRequesterEntityId(), + samlAuthenticationRequest.getRequestId(), + user.getUid(), + samlStatus, + relayState, + optionalMessage, + authnContextClassRefValue, + attributes, + response + ); } public ServiceProviderResolver getServiceProviderResolver() { return serviceProviderResolver; } - protected List attributes(User user, String requesterEntityId) { + public void setServiceProviderResolver(ServiceProviderResolver serviceProviderResolver) { + this.serviceProviderResolver = serviceProviderResolver; + } + + public void setUserRepository(UserRepository userRepository) { + this.userRepository = userRepository; + } + + protected List attributes(User user, String requesterEntityId) { List linkedAccounts = safeSortedAffiliations(user); String givenName = user.getGivenName(); String familyName = user.getFamilyName(); @@ -727,7 +702,7 @@ protected List attributes(User user, String requesterEntityId) { String displayName = String.format("%s %s", chosenName, familyName); String commonName = String.format("%s %s", givenName, familyName); String eppn = user.getEduPersonPrincipalName(); - List attributes = new ArrayList(Arrays.asList( + List attributes = new ArrayList(Arrays.asList( attribute("urn:mace:dir:attribute-def:cn", displayName), attribute("urn:mace:dir:attribute-def:displayName", displayName), attribute("urn:mace:dir:attribute-def:commonName", commonName), @@ -745,7 +720,9 @@ protected List attributes(User user, String requesterEntityId) { attributes.add(attribute("urn:mace:eduid.nl:1.1", eduIDValue)); user.getAttributes() - .forEach((key, value) -> attributes.add(attribute(key, value.toArray(new String[]{})))); + .forEach((key, values) -> + values.forEach(value -> attributes.add(attribute(key, value)))); + List scopedAffiliations = linkedAccounts.stream() .map(linkedAccount -> linkedAccount.getEduPersonAffiliations().stream() @@ -754,13 +731,11 @@ protected List attributes(User user, String requesterEntityId) { .collect(toList())) .flatMap(Collection::stream).distinct().collect(Collectors.toList()); scopedAffiliations.add("affiliate@eduid.nl"); - attributes.add(attribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation", - scopedAffiliations.toArray(new String[]{}))); + scopedAffiliations.forEach(aff -> attributes.add(attribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation", aff))); List affiliations = scopedAffiliations.stream().map(affiliation -> affiliation.substring(0, affiliation.indexOf("@"))) .distinct().collect(toList()); - attributes.add(attribute("urn:mace:dir:attribute-def:eduPersonAffiliation", - affiliations.toArray(new String[]{}))); + scopedAffiliations.forEach(aff -> attributes.add(attribute("urn:mace:dir:attribute-def:eduPersonAffiliation", aff))); return attributes; } @@ -777,8 +752,8 @@ private List safeSortedAffiliations(User user) { return user.linkedAccountsSorted(); } - private Attribute attribute(String name, String... value) { - return new Attribute().setName(name).setNameFormat(AttributeNameFormat.URI).addValues((Object[]) value); + private SAMLAttribute attribute(String name, String value) { + return new SAMLAttribute(name, value); } } diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index cfca48f6..3762afcf 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -1,11 +1,10 @@ package myconext.security; -import myconext.config.BeanConfig; +import lombok.SneakyThrows; import myconext.crypto.KeyGenerator; import myconext.geo.GeoLocation; import myconext.mail.MailBox; import myconext.manage.ServiceProviderResolver; -import myconext.model.ServiceProvider; import myconext.repository.AuthenticationRequestRepository; import myconext.repository.UserLoginRepository; import myconext.repository.UserRepository; @@ -29,24 +28,18 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.saml.key.SimpleKey; -import org.springframework.security.saml.provider.config.RotatingKeys; -import org.springframework.security.saml.provider.identity.config.ExternalServiceProviderConfiguration; -import org.springframework.security.saml.provider.identity.config.SamlIdentityProviderSecurityConfiguration; -import org.springframework.security.saml.provider.identity.config.SamlIdentityProviderSecurityDsl; -import org.springframework.security.saml.saml2.metadata.NameId; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; +import saml.model.SAMLConfiguration; +import saml.model.SAMLIdentityProvider; +import saml.model.SAMLServiceProvider; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; -import static org.springframework.security.saml.saml2.signature.AlgorithmMethod.RSA_SHA512; -import static org.springframework.security.saml.saml2.signature.DigestMethod.SHA512; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -56,11 +49,9 @@ public class SecurityConfiguration { @Configuration @Order(1) - public static class SamlSecurity { + public static class SamlSecurity extends WebSecurityConfigurerAdapter { - private final Resource privateKeyPath; - private final Resource certificatePath; - private final String idpEntityId; + private final GuestIdpAuthenticationRequestFilter guestIdpAuthenticationRequestFilter; public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, @Value("${certificate_path}") Resource certificatePath, @@ -88,17 +79,40 @@ public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, GeoLocation geoLocation, MailBox mailBox, ServiceProviderResolver serviceProviderResolver) { - List serviceProviders = new ArrayList<>(); - - this.privateKeyPath = privateKeyPath; - this.certificatePath = certificatePath; - this.idpEntityId = idpEntityId; + String[] keys = this.getKeys(certificatePath, privateKeyPath); + final List serviceProviders = new ArrayList<>(); List spEntityIdentifiers = commaSeparatedToList(spEntityId); List spMetaDataUrls = commaSeparatedToList(spMetaDataUrl); for (int i = 0; i < spEntityIdentifiers.size(); i++) { - serviceProviders.add(new ServiceProvider(spEntityIdentifiers.get(i), spMetaDataUrls.get(i))); + serviceProviders.add(new SAMLServiceProvider(spEntityIdentifiers.get(i), spMetaDataUrls.get(i))); } + + SAMLConfiguration configuration = new SAMLConfiguration( + new SAMLIdentityProvider(keys[0], keys[1], idpEntityId), + serviceProviders, + true + ); + this.guestIdpAuthenticationRequestFilter = new GuestIdpAuthenticationRequestFilter( + redirectUrl, + serviceProviderResolver, + authenticationRequestRepository, + userRepository, + userLoginRepository, + geoLocation, + rememberMeMaxAge, + nudgeAppDays, + rememberMeQuestionAskedDays, + secureCookie, + magicLinkUrl, + mailBox, + expiryNonValidatedDurationDays, + ssoMFADurationSeconds, + mobileAppROEntityId, + featureDefaultRememberMe, + configuration + ); + } private List commaSeparatedToList(String spEntityId) { @@ -118,54 +132,21 @@ protected void configure(HttpSecurity http) throws Exception { ) .authorizeRequests() .antMatchers("/**").hasRole("GUEST"); - - //TODO add GuestIdpAuthenticationRequestFilter before AbstractPreAuthenticatedProcessingFilter - - - super.configure(http); - - String prefix = getPrefix(); - SamlIdentityProviderSecurityDsl configurer = new GuestIdentityProviderDsl(beanConfig); - - SamlIdentityProviderSecurityDsl samlIdentityProviderSecurityDsl = http.apply(configurer) - .prefix(prefix) - .useStandardFilters(false) - .entityId(idpEntityId) - .alias("guest-idp") - .singleLogout(false) - .signMetadata(true) - .signatureAlgorithms(RSA_SHA512, SHA512) - .nameIds(asList(NameId.PERSISTENT)) - .rotatingKeys(getKeys()); - serviceProviders.forEach(sp -> samlIdentityProviderSecurityDsl.serviceProvider( - new ExternalServiceProviderConfiguration() - .setAlias(sp.getEntityId()) - .setMetadata(sp.getMetaDataUrl()) - .setSkipSslValidation(false) - - )); } - private RotatingKeys getKeys() throws Exception { + @SneakyThrows + private String[] getKeys(Resource certificatePath, Resource privateKeyPath) { String privateKey; String certificate; - if (this.privateKeyPath.exists() && this.certificatePath.exists()) { - privateKey = read(this.privateKeyPath); - certificate = read(this.certificatePath); + if (privateKeyPath.exists() && certificatePath.exists()) { + privateKey = read(privateKeyPath); + certificate = read(certificatePath); } else { String[] keys = KeyGenerator.generateKeys(); privateKey = keys[0]; certificate = keys[1]; } - return new RotatingKeys() - .setActive( - new SimpleKey() - .setName("idp-signing-key") - .setPrivateKey(privateKey) - //to prevent null-pointer in SamlKeyStoreProvider - .setPassphrase("") - .setCertificate(certificate) - ); + return new String[]{certificate, privateKey}; } private String read(Resource resource) throws IOException { diff --git a/myconext-server/src/main/java/myconext/session/SessionConfig.java b/myconext-server/src/main/java/myconext/session/SessionConfig.java index aee4d893..005e1062 100644 --- a/myconext-server/src/main/java/myconext/session/SessionConfig.java +++ b/myconext-server/src/main/java/myconext/session/SessionConfig.java @@ -14,7 +14,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import org.springframework.security.saml.saml2.authentication.Assertion; import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; import org.springframework.session.web.http.CookieSerializer; @@ -37,7 +36,6 @@ public ObjectMapper jsonMapper() { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .registerModule(new Jdk8Module()) .registerModule(new JavaTimeModule()) - .addMixIn(Assertion.class, AssertionMixin.class) .addMixIn(HashSet.class, HashSetMixin.class) .addMixIn(User.class, UserMixin.class) .addMixIn(LinkedAccount.class, LinkedAccountMixin.class) @@ -57,9 +55,6 @@ CookieSerializer cookieSerializer(@Value("${secure_cookie}") boolean secureCooki return defaultCookieSerializer; } - private static class AssertionMixin { - } - private static class HashSetMixin { } diff --git a/myconext-server/src/test/java/myconext/security/GuestIdpAuthenticationRequestFilterTest.java b/myconext-server/src/test/java/myconext/security/GuestIdpAuthenticationRequestFilterTest.java index 804b2af6..46bdfaa3 100644 --- a/myconext-server/src/test/java/myconext/security/GuestIdpAuthenticationRequestFilterTest.java +++ b/myconext-server/src/test/java/myconext/security/GuestIdpAuthenticationRequestFilterTest.java @@ -1,14 +1,13 @@ package myconext.security; -import myconext.geo.GeoLocation; import myconext.manage.MockServiceProviderResolver; import myconext.model.LinkedAccount; import myconext.model.User; -import myconext.repository.UserLoginRepository; import myconext.repository.UserRepository; import org.junit.Test; +import org.junit.Before; import org.mockito.Mockito; -import org.springframework.security.saml.saml2.attribute.Attribute; +import saml.model.SAMLAttribute; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -23,26 +22,14 @@ public class GuestIdpAuthenticationRequestFilterTest { - private final int expiryNonValidatedDurationDays = 180; - - private final GuestIdpAuthenticationRequestFilter subject = new GuestIdpAuthenticationRequestFilter(null, - null, - null, - new MockServiceProviderResolver(), - null, - Mockito.mock(UserRepository.class), - Mockito.mock(UserLoginRepository.class), - Mockito.mock(GeoLocation.class), - 90, - 1, - 1, - false, - null, - null, - expiryNonValidatedDurationDays, - 60 * 15, - "mobile_app_rp_entity_id", - false); + private final GuestIdpAuthenticationRequestFilter subject = + new GuestIdpAuthenticationRequestFilter(); + + @Before + public void beforeEach() { + subject.setServiceProviderResolver(new MockServiceProviderResolver()); + subject.setUserRepository(Mockito.mock(UserRepository.class)); + } @Test public void isUserVerifiedByInstitutionNoLinkedAccounts() { @@ -127,11 +114,11 @@ public void attributes() { linkedAccount("Mark", "Lee", createdAt(25)) ); user.setLinkedAccounts(linkedAccounts); - List attributes = subject.attributes(user, "requesterEntityID"); - String givenName = (String) attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:givenName")).findFirst().get().getValues().get(0); - String familyName = (String) attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:sn")).findFirst().get().getValues().get(0); - String displayName = (String) attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:displayName")).findFirst().get().getValues().get(0); - String commonName = (String) attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:commonName")).findFirst().get().getValues().get(0); + List attributes = subject.attributes(user, "requesterEntityID"); + String givenName = attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:givenName")).findFirst().get().getValue(); + String familyName = attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:sn")).findFirst().get().getValue(); + String displayName = attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:displayName")).findFirst().get().getValue(); + String commonName = attributes.stream().filter(attr -> attr.getName().equals("urn:mace:dir:attribute-def:commonName")).findFirst().get().getValue(); assertEquals("Mary", givenName); assertEquals("Poppins", familyName); From ea4bb3911aa353a33367df15b7ed6e49672f7e60 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 6 Nov 2023 16:31:10 +0100 Subject: [PATCH 7/9] Start fixing all the broken tests... --- myconext-server/pom.xml | 5 ----- .../src/main/java/myconext/geo/MaxMindGeoLocation.java | 7 ++++--- .../myconext/manage/ResourceServiceProviderResolver.java | 8 +++++--- .../java/myconext/security/SecurityConfiguration.java | 5 +++-- .../src/main/java/myconext/sms/SMSServiceImpl.java | 7 ++++--- .../src/test/java/myconext/AbstractIntegrationTest.java | 8 ++++---- .../src/test/java/myconext/api/UserControllerTest.java | 5 +++-- .../src/test/java/myconext/eduid/APIControllerTest.java | 7 ++----- .../test/java/myconext/geo/MaxMindGeoLocationTest.java | 9 +++++---- .../src/test/java/myconext/tiqr/TiqrControllerTest.java | 4 ++-- 10 files changed, 32 insertions(+), 33 deletions(-) diff --git a/myconext-server/pom.xml b/myconext-server/pom.xml index 0ce8ebd3..b26f7481 100644 --- a/myconext-server/pom.xml +++ b/myconext-server/pom.xml @@ -125,11 +125,6 @@ compiler 0.9.10 - - commons-io - commons-io - 20030203.000550 - org.openconext tiqr-java-connector diff --git a/myconext-server/src/main/java/myconext/geo/MaxMindGeoLocation.java b/myconext-server/src/main/java/myconext/geo/MaxMindGeoLocation.java index 5b802766..3131eb9d 100644 --- a/myconext-server/src/main/java/myconext/geo/MaxMindGeoLocation.java +++ b/myconext-server/src/main/java/myconext/geo/MaxMindGeoLocation.java @@ -9,7 +9,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; @@ -19,6 +19,7 @@ import java.io.*; import java.net.InetAddress; import java.net.URL; +import java.nio.charset.Charset; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.*; @@ -116,7 +117,7 @@ public void refresh() { FileOutputStream fileOutputStream = new FileOutputStream(file); InputStream inputStream = new URL(String.format(urlTemplate, licenseKey)).openStream(); - IOUtil.copy(inputStream, fileOutputStream); + IOUtils.copy(inputStream, fileOutputStream); TarArchiveInputStream fin = new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(file))); ArchiveEntry entry; @@ -125,7 +126,7 @@ public void refresh() { if (entry.getName().endsWith("mmdb")) { binaryData = new File(String.format("%s/%s/%s", this.downloadDirectory, name, GEO_LITE_2_CITY_MMDB)); try (OutputStream o = Files.newOutputStream(binaryData.toPath())) { - IOUtil.copy(fin, o); + IOUtils.copy(fin, o); } break; } diff --git a/myconext-server/src/main/java/myconext/manage/ResourceServiceProviderResolver.java b/myconext-server/src/main/java/myconext/manage/ResourceServiceProviderResolver.java index 9913ce8a..7da03493 100644 --- a/myconext-server/src/main/java/myconext/manage/ResourceServiceProviderResolver.java +++ b/myconext-server/src/main/java/myconext/manage/ResourceServiceProviderResolver.java @@ -4,9 +4,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import myconext.model.ServiceProvider; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.springframework.core.io.Resource; +import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Optional; @@ -17,8 +18,9 @@ public class ResourceServiceProviderResolver implements ServiceProviderResolver @SneakyThrows public ResourceServiceProviderResolver(Resource resource, ObjectMapper objectMapper) { - spNames = objectMapper.readValue(IOUtil.toString(resource.getInputStream()), new TypeReference<>() { - }); + spNames = objectMapper.readValue(IOUtils.toString(resource.getInputStream(), Charset.defaultCharset()), + new TypeReference<>() { + }); } @Override diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index 3762afcf..93415090 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -11,7 +11,7 @@ import myconext.shibboleth.ShibbolethPreAuthenticatedProcessingFilter; import myconext.shibboleth.ShibbolethUserDetailService; import myconext.shibboleth.mock.MockShibbolethFilter; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +35,7 @@ import saml.model.SAMLServiceProvider; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -151,7 +152,7 @@ private String[] getKeys(Resource certificatePath, Resource privateKeyPath) { private String read(Resource resource) throws IOException { LOG.info("Reading resource: " + resource.getFilename()); - return IOUtil.toString(resource.getInputStream()); + return IOUtils.toString(resource.getInputStream(), Charset.defaultCharset()); } } diff --git a/myconext-server/src/main/java/myconext/sms/SMSServiceImpl.java b/myconext-server/src/main/java/myconext/sms/SMSServiceImpl.java index b7ec784a..b5be85a4 100644 --- a/myconext-server/src/main/java/myconext/sms/SMSServiceImpl.java +++ b/myconext-server/src/main/java/myconext/sms/SMSServiceImpl.java @@ -1,13 +1,14 @@ package myconext.sms; import lombok.SneakyThrows; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.springframework.core.io.ClassPathResource; import org.springframework.http.*; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.net.URI; +import java.nio.charset.Charset; import java.util.List; import java.util.Locale; import java.util.Map; @@ -24,8 +25,8 @@ public class SMSServiceImpl implements SMSService { @SneakyThrows public SMSServiceImpl(String url, String bearer){ this.url = url; - this.templateNl = IOUtil.toString(new ClassPathResource("sms/template_nl.txt").getInputStream()); - this.templateEn = IOUtil.toString(new ClassPathResource("sms/template_en.txt").getInputStream()); + this.templateNl = IOUtils.toString(new ClassPathResource("sms/template_nl.txt").getInputStream(), Charset.defaultCharset()); + this.templateEn = IOUtils.toString(new ClassPathResource("sms/template_en.txt").getInputStream(), Charset.defaultCharset()); headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); headers.add(HttpHeaders.AUTHORIZATION, "Bearer "+ bearer); diff --git a/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java b/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java index 28a245da..ba9bce6a 100644 --- a/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java +++ b/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java @@ -15,7 +15,7 @@ import myconext.model.*; import myconext.repository.*; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.jupiter.api.BeforeEach; import org.junit.runner.RunWith; @@ -207,7 +207,7 @@ protected Response samlAuthnRequestResponseWithLoa(Cookie cookie, String relaySt } public static String readFile(String path) throws IOException { - return IOUtil.toString(new ClassPathResource(path).getInputStream()); + return IOUtils.toString(new ClassPathResource(path).getInputStream(), Charset.defaultCharset()); } private String deflatedBase64encoded(String input) throws IOException { @@ -258,7 +258,7 @@ protected String doOpaqueAccessToken(boolean valid, String[] scopes, String file scopeList.add("openid"); String file = String.format("oidc/%s.json", valid ? filePart : "introspect-invalid-token"); - String introspectResult = IOUtil.toString(new ClassPathResource(file).getInputStream()); + String introspectResult = IOUtils.toString(new ClassPathResource(file).getInputStream(), Charset.defaultCharset()); String introspectResultWithScope = valid ? String.format(introspectResult, String.join(" ", scopeList)) : introspectResult; stubFor(post(urlPathMatching("/introspect")).willReturn(aResponse() .withHeader("Content-Type", "application/json") @@ -306,7 +306,7 @@ protected String samlAuthnResponse(Response response, Optional optionalC response = this.get302Response(response, optionalCookieFilter); } assertEquals(HttpStatus.OK.value(), response.getStatusCode()); - String html = IOUtil.toString(response.asInputStream()); + String html = IOUtils.toString(response.asInputStream(), Charset.defaultCharset()); Matcher matcher = Pattern.compile("name=\"SAMLResponse\" value=\"(.*?)\"").matcher(html); matcher.find(); diff --git a/myconext-server/src/test/java/myconext/api/UserControllerTest.java b/myconext-server/src/test/java/myconext/api/UserControllerTest.java index d058884e..22a0271c 100644 --- a/myconext-server/src/test/java/myconext/api/UserControllerTest.java +++ b/myconext-server/src/test/java/myconext/api/UserControllerTest.java @@ -13,7 +13,7 @@ import myconext.repository.ChallengeRepository; import myconext.security.ACR; import myconext.tiqr.SURFSecureID; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.apache.http.client.CookieStore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +27,7 @@ import org.springframework.util.StringUtils; import java.io.IOException; +import java.nio.charset.Charset; import java.security.SecureRandom; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -250,7 +251,7 @@ public void relayState() throws IOException { String authenticationRequestId = samlAuthnRequest("Nice"); MagicLinkResponse magicLinkResponse = magicLinkRequest(new MagicLinkRequest(authenticationRequestId, user, false), HttpMethod.POST); Response response = magicResponse(magicLinkResponse); - assertTrue(IOUtil.toString(response.asInputStream()).contains("Nice")); + assertTrue(IOUtils.toString(response.asInputStream(), Charset.defaultCharset()).contains("Nice")); } @Test diff --git a/myconext-server/src/test/java/myconext/eduid/APIControllerTest.java b/myconext-server/src/test/java/myconext/eduid/APIControllerTest.java index be24f630..f76f4c84 100644 --- a/myconext-server/src/test/java/myconext/eduid/APIControllerTest.java +++ b/myconext-server/src/test/java/myconext/eduid/APIControllerTest.java @@ -3,15 +3,12 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import io.restassured.http.ContentType; import myconext.AbstractIntegrationTest; -import org.apache.commons.io.IOUtil; import org.junit.ClassRule; import org.junit.Test; -import org.springframework.core.io.ClassPathResource; -import java.io.IOException; -import java.util.*; +import java.util.List; +import java.util.Map; -import static com.github.tomakehurst.wiremock.client.WireMock.*; import static io.restassured.RestAssured.given; import static org.junit.Assert.assertEquals; diff --git a/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java b/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java index 9771760c..4b85044a 100644 --- a/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java +++ b/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java @@ -2,13 +2,14 @@ import myconext.WireMockExtension; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.core.io.ClassPathResource; +import java.io.File; import java.io.IOException; import static com.github.tomakehurst.wiremock.client.WireMock.*; @@ -25,7 +26,7 @@ class MaxMindGeoLocationTest { @BeforeEach void before() throws IOException { String path = "/geo/GeoLite2-City_20220101.tar.gz"; - byte[] body = IOUtil.toByteArray(new ClassPathResource(path).getInputStream()); + byte[] body = IOUtils.toByteArray(new ClassPathResource(path).getInputStream()); stubFor(get(urlPathMatching("/maxmind")) .willReturn(aResponse() .withStatus(200) @@ -36,8 +37,8 @@ void before() throws IOException { @AfterEach void after() throws IOException { - String s = System.getProperty("java.io.tmpdir"); - FileUtils.forceDelete(s + "/geo"); + File file = new File(System.getProperty("java.io.tmpdir"+ "/geo")); + FileUtils.forceDelete(file); } @Test diff --git a/myconext-server/src/test/java/myconext/tiqr/TiqrControllerTest.java b/myconext-server/src/test/java/myconext/tiqr/TiqrControllerTest.java index 1ca182c2..562df734 100644 --- a/myconext-server/src/test/java/myconext/tiqr/TiqrControllerTest.java +++ b/myconext-server/src/test/java/myconext/tiqr/TiqrControllerTest.java @@ -9,7 +9,7 @@ import myconext.model.MagicLinkRequest; import myconext.model.SamlAuthenticationRequest; import myconext.model.User; -import org.apache.commons.io.IOUtil; +import org.apache.commons.io.IOUtils; import org.junit.Test; import org.springframework.http.HttpMethod; import org.springframework.web.util.UriComponents; @@ -383,7 +383,7 @@ public void startAuthentication() throws Exception { List.of("TIQR_COOKIE=true", "BROWSER_SESSION=true", "TRACKING_DEVICE=", "SESSION=") .forEach(s -> assertTrue(cookies.stream().anyMatch(cookie -> cookie.startsWith(s)))); - String html = IOUtil.toString(response.asInputStream()); + String html = IOUtils.toString(response.asInputStream(), Charset.defaultCharset()); Matcher matcher = Pattern.compile("name=\"SAMLResponse\" value=\"(.*?)\"").matcher(html); matcher.find(); From 883dd1324f25411ffb8db63663437710a4603be0 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 6 Nov 2023 17:48:26 +0100 Subject: [PATCH 8/9] WIP for fixing test after sam-migration --- .../security/GuestIdpAuthenticationRequestFilter.java | 6 +++++- .../main/java/myconext/security/SecurityConfiguration.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 807143c9..8b059132 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -286,7 +286,11 @@ public static boolean hasRequiredStudentAffiliation(List affiliations) { } private List getAuthenticationContextClassReferenceValues(AuthnRequest authenticationRequest) { - List authnContextClassRefs = authenticationRequest.getRequestedAuthnContext().getAuthnContextClassRefs(); + RequestedAuthnContext requestedAuthnContext = authenticationRequest.getRequestedAuthnContext(); + if (requestedAuthnContext == null) { + return Collections.emptyList(); + } + List authnContextClassRefs = requestedAuthnContext.getAuthnContextClassRefs(); if (authnContextClassRefs == null) { return Collections.emptyList(); } diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index 93415090..04b7744b 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -92,7 +92,7 @@ public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, SAMLConfiguration configuration = new SAMLConfiguration( new SAMLIdentityProvider(keys[0], keys[1], idpEntityId), serviceProviders, - true + false ); this.guestIdpAuthenticationRequestFilter = new GuestIdpAuthenticationRequestFilter( redirectUrl, From c364e4303a3b84edde5f7a6c0867be17e255d9d8 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Tue, 7 Nov 2023 14:59:17 +0100 Subject: [PATCH 9/9] Fixed tests after saml migration --- .gitignore | 3 ++- myconext-server/pom.xml | 14 +++++++------- .../GuestIdpAuthenticationRequestFilter.java | 6 ++++-- .../myconext/security/SecurityConfiguration.java | 1 + myconext-server/src/main/resources/application.yml | 6 ++++-- .../java/myconext/AbstractIntegrationTest.java | 3 ++- .../java/myconext/geo/MaxMindGeoLocationTest.java | 2 +- pom.xml | 8 ++++---- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index bb0a38c4..f6427d94 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ local.application.yml .classpath log NOTES.md -dep.tree \ No newline at end of file +dep.tree +application-test2.yml \ No newline at end of file diff --git a/myconext-server/pom.xml b/myconext-server/pom.xml index b26f7481..5215b547 100644 --- a/myconext-server/pom.xml +++ b/myconext-server/pom.xml @@ -45,15 +45,10 @@ org.springframework.boot spring-boot-starter-web - - - - - org.openconext saml-idp - 0.0.1-SNAPSHOT + 0.0.4-SNAPSHOT org.springframework.boot @@ -125,10 +120,15 @@ compiler 0.9.10 + + org.apache.commons + commons-lang3 + 3.13.0 + org.openconext tiqr-java-connector - 1.1.0 + 1.1.2 com.fasterxml.jackson.datatype diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 8b059132..047994da 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -664,7 +664,9 @@ private void sendAssertion(HttpServletRequest request, String.join(", ", authenticationContextClassReferences)); samlStatus = SAMLStatus.NO_AUTHN_CONTEXT; } - + if (!samlStatus.equals(SAMLStatus.SUCCESS)) { + authnContextClassRefValue = DefaultSAMLIdPService.authnContextClassRefUnspecified; + } Optional optionalCookie = cookieByName(request, BROWSER_SESSION_COOKIE_NAME); optionalCookie.ifPresent(cookie -> { cookie.setMaxAge(0); @@ -673,7 +675,7 @@ private void sendAssertion(HttpServletRequest request, //Tracking cookie for user new device discovery this.addTrackingCookie(request, response, user); this.samlIdpService.sendResponse( - samlAuthenticationRequest.getRequesterEntityId(), + samlAuthenticationRequest.getIssuer(), samlAuthenticationRequest.getRequestId(), user.getUid(), samlStatus, diff --git a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java index 04b7744b..536d0baf 100644 --- a/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java +++ b/myconext-server/src/main/java/myconext/security/SecurityConfiguration.java @@ -74,6 +74,7 @@ public SamlSecurity(@Value("${private_key_path}") Resource privateKeyPath, @Value("${sso_mfa_duration_seconds}") long ssoMFADurationSeconds, @Value("${mobile_app_rp_entity_id}") String mobileAppROEntityId, @Value("${feature.default_remember_me}") boolean featureDefaultRememberMe, + @Value("${feature.requires_signed_authn_request}") boolean requiresSignedAuthnRequest, AuthenticationRequestRepository authenticationRequestRepository, UserRepository userRepository, UserLoginRepository userLoginRepository, diff --git a/myconext-server/src/main/resources/application.yml b/myconext-server/src/main/resources/application.yml index 133841bd..cfa17e91 100644 --- a/myconext-server/src/main/resources/application.yml +++ b/myconext-server/src/main/resources/application.yml @@ -61,8 +61,8 @@ idp_redirect_url: http://localhost:3000 rp_id: localhost rp_origin: http://localhost:3000 sp_redirect_url: http://localhost:3001 -sp_entity_id: https://engine.test2.surfconext.nl/authentication/sp/metadata, https://engine.test.surfconext.nl/authentication/sp/metadata -sp_entity_metadata_url: https://engine.test2.surfconext.nl/authentication/sp/metadata, https://engine.test.surfconext.nl/authentication/sp/metadata +sp_entity_id: https://engine.test.surfconext.nl/authentication/sp/metadata +sp_entity_metadata_url: https://engine.test.surfconext.nl/authentication/sp/metadata guest_idp_entity_id: https://localhost.surf.id my_conext_url: https://my.test2.surfconext.nl domain: eduid.nl @@ -90,6 +90,8 @@ feature: create_eduid_institution_landing: True # Do we default remember the user for a longer period default_remember_me: False + # Does the SAMLIdpService expects authn requests to be signed + requires_signed_authn_request: True secure_cookie: false idp_entity_id: https://localhost.surf.id diff --git a/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java b/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java index ba9bce6a..b37fa692 100644 --- a/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java +++ b/myconext-server/src/test/java/myconext/AbstractIntegrationTest.java @@ -80,7 +80,8 @@ "eduid_api.oidcng_introspection_uri=http://localhost:8098/introspect", "cron.service-name-resolver-initial-delay-milliseconds=60000", "oidc.base-url=http://localhost:8098/", - "sso_mfa_duration_seconds=-1000" + "sso_mfa_duration_seconds=-1000", + "feature.requires_signed_authn_request=false" }) @ActiveProfiles({"test"}) @SuppressWarnings("unchecked") diff --git a/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java b/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java index 4b85044a..d709944d 100644 --- a/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java +++ b/myconext-server/src/test/java/myconext/geo/MaxMindGeoLocationTest.java @@ -37,7 +37,7 @@ void before() throws IOException { @AfterEach void after() throws IOException { - File file = new File(System.getProperty("java.io.tmpdir"+ "/geo")); + File file = new File(System.getProperty("java.io.tmpdir") + "/geo"); FileUtils.forceDelete(file); } diff --git a/pom.xml b/pom.xml index 5b7d92b0..1dc515e0 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.13 + 2.7.17 @@ -30,7 +30,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.9.0 + 3.11.0 11 @@ -38,7 +38,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0 + 3.3.0 enforce-versions @@ -78,7 +78,7 @@ org.apache.maven.wagon wagon-webdav-jackrabbit - 3.5.1 + 3.5.3