Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ subprojects {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.opensaml' && details.requested.name.startsWith("opensaml-")) {
details.useVersion "${versions.opensaml}"
details.because 'Spring Security 5.8.x allows OpenSAML 3 or 4. OpenSAML 3 has reached its end-of-life. Spring Security 6 drops support for 3, using 4.'
details.because 'Pinning all opensaml modules to the same version for OpenSAML 5 migration.'
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ versions.springBootVersion = "3.5.14"
versions.guavaVersion = "33.6.0-jre"
versions.seleniumVersion = "4.43.0"
versions.braveVersion = "6.3.1"
versions.opensaml = "4.3.2"
// OpenSAML 5.2.x pulls non-FIPS classes; stay on 5.1.x until resolved
versions.opensaml = "5.1.6"

// Versions we're overriding from the Spring Boot Bom (Dependabot does not issue PRs to bump these versions, so we need to manually bump them)
ext["selenium.version"] = "${versions.seleniumVersion}" // Selenium for integration tests only
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.cloudfoundry.identity.uaa.authentication;

import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator;
Comment thread
strehle marked this conversation as resolved.
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult;
Expand All @@ -17,7 +17,7 @@ public class SamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
private final Saml2LogoutRequestValidator delegate;

public SamlLogoutRequestValidator() {
this.delegate = new OpenSamlLogoutRequestValidator();
this.delegate = new OpenSaml5LogoutRequestValidator();
}
Comment thread
strehle marked this conversation as resolved.

public SamlLogoutRequestValidator(Saml2LogoutRequestValidator delegate) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.cloudfoundry.identity.uaa.authentication;

import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator;
Comment thread
strehle marked this conversation as resolved.
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidatorParameters;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult;
Expand All @@ -18,7 +18,7 @@ public class SamlLogoutResponseValidator implements Saml2LogoutResponseValidator
private final Saml2LogoutResponseValidator delegate;

public SamlLogoutResponseValidator() {
this.delegate = new OpenSamlLogoutResponseValidator();
this.delegate = new OpenSaml5LogoutResponseValidator();
}
Comment thread
strehle marked this conversation as resolved.

public SamlLogoutResponseValidator(Saml2LogoutResponseValidator delegate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import jakarta.annotation.Nonnull;
import lombok.Getter;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import net.shibboleth.shared.xml.ParserPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.util.UaaUrlUtils;
Expand Down Expand Up @@ -106,13 +106,17 @@
import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.normalizeUrlForPortComparison;

/**
* This was copied from Spring Security, and modified to work with Open SAML 4.0.x
* The original class only works with Open SAML 4.1.x+
* This was originally copied from Spring Security, and modified to work with Open SAML 4.0.x,
* then further updated for Open SAML 5.x compatibility.
* <p/>
* Once we can move to the spring-security version of OpenSaml4AuthenticationProvider,
* this class should be removed, along with OpenSamlDecryptionUtils and OpenSamlVerificationUtils.
* Key changes from OpenSAML 4 to 5:
* - {@code net.shibboleth.utilities.java.support} packages moved to {@code net.shibboleth.shared}
* - {@code SAML20AssertionValidator} constructor gains a 4th {@code AssertionValidator} parameter
* - {@code ValidationContext.getValidationFailureMessage()} renamed to {@code getValidationFailureMessages()}
* - {@code ConditionValidator.validate()} now throws {@code AssertionValidationException}
* - {@code BearerSubjectConfirmationValidator.validateAddress()} removed (address validation dropped)
*/
public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider, ZoneAware {
public final class OpenSaml5AuthenticationProvider implements AuthenticationProvider, ZoneAware {

static {
SamlConfiguration.setupOpenSaml();
Expand Down Expand Up @@ -147,9 +151,9 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
private Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter = createDefaultResponseAuthenticationConverter();

/**
* Creates an {@link OpenSaml4AuthenticationProvider}
* Creates an {@link OpenSaml5AuthenticationProvider}
*/
public OpenSaml4AuthenticationProvider() {
public OpenSaml5AuthenticationProvider() {
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory()
.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME);
Expand All @@ -163,7 +167,7 @@ public OpenSaml4AuthenticationProvider() {
* {@link #createDefaultResponseValidator()}, like so:
*
* <pre>
* OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
* OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
* provider.setResponseValidator(responseToken -&gt; {
* Saml2ResponseValidatorResult result = createDefaultResponseValidator()
* .convert(responseToken)
Expand Down Expand Up @@ -579,7 +583,7 @@ private static Converter<AssertionToken, Saml2ResponseValidatorResult> createAss
}
String message = "Invalid assertion [%s] for SAML response [%s]: %s".formatted(assertion.getID(),
assertion.getParent() != null ? ((Response) assertion.getParent()).getID() : assertion.getID(),
context.getValidationFailureMessage());
String.join("; ", context.getValidationFailureMessages()));
return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message));
};
}
Expand Down Expand Up @@ -619,6 +623,8 @@ private static ValidationContext createValidationContext(AssertionToken assertio
params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience));
params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, recipientList);
params.put(SAML2AssertionValidationParameters.VALID_ISSUERS, Collections.singleton(assertingPartyEntityId));
// Disable address checking - we don't track valid client addresses
params.put(SAML2AssertionValidationParameters.SC_CHECK_ADDRESS, false);
paramsConsumer.accept(params);
return new ValidationContext(params);
}
Expand Down Expand Up @@ -699,18 +705,11 @@ public ValidationResult validate(Condition condition, Assertion assertion, Valid
}
});
conditions.add(new ProxyRestrictionConditionValidator());
subjects.add(new BearerSubjectConfirmationValidator() {
@Override
protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion,
ValidationContext context, boolean required) {
// applications should validate their own addresses - gh-7514
return ValidationResult.VALID;
}
});
subjects.add(new BearerSubjectConfirmationValidator());
}

private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions,
subjects, statements, null, null) {
subjects, statements, null, null, null) {
@Nonnull
@Override
protected ValidationResult validateSignature(Assertion token, ValidationContext context) {
Expand All @@ -719,7 +718,7 @@ protected ValidationResult validateSignature(Assertion token, ValidationContext
};

static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) {
return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), engine,
return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null, engine,
validator) {
@Nonnull
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
import java.util.Collection;

/**
* This class was copied from Spring Security 5.6.0 to get the OpenSaml4AuthenticationProvider to work.
* It should be removed once we are able to more to the spring-security version of OpenSaml4AuthenticationProvider.
* This class was copied from Spring Security 5.6.0 to get the OpenSaml5AuthenticationProvider to work.
* It should be removed once we are able to move to the spring-security version of OpenSaml5AuthenticationProvider.
* <p/>
* Utility methods for decrypting SAML components with OpenSAML
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package org.cloudfoundry.identity.uaa.provider.saml;

import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.shared.resolver.CriteriaSet;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.ProtocolCriterion;
Expand Down Expand Up @@ -48,8 +48,8 @@
import java.util.Set;

/**
* This class was copied from Spring Security 5.6.0 to get the OpenSaml4AuthenticationProvider to work.
* It should be removed once we are able to more to the spring-security version of OpenSaml4AuthenticationProvider.
* This class was copied from Spring Security 5.6.0 to get the OpenSaml5AuthenticationProvider to work.
* It should be removed once we are able to move to the spring-security version of OpenSaml5AuthenticationProvider.
* <p/>
* Utility methods for verifying SAML component signatures with OpenSAML
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package org.cloudfoundry.identity.uaa.provider.saml;

import lombok.extern.slf4j.Slf4j;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import net.shibboleth.shared.xml.ParserPool;
import org.cloudfoundry.identity.uaa.authentication.BackwardsCompatibleTokenEndpointAuthenticationFilter;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager;
Expand Down Expand Up @@ -60,7 +60,7 @@
import java.util.Optional;
import java.util.function.Consumer;

import static org.cloudfoundry.identity.uaa.provider.saml.OpenSaml4AuthenticationProvider.createDefaultAssertionValidatorWithParameters;
import static org.cloudfoundry.identity.uaa.provider.saml.OpenSaml5AuthenticationProvider.createDefaultAssertionValidatorWithParameters;

/**
* This {@link AuthenticationConverter} is used in the SAML2 Bearer Grant exchange in {@link BackwardsCompatibleTokenEndpointAuthenticationFilter}
Expand Down Expand Up @@ -88,13 +88,13 @@ public final class Saml2BearerGrantAuthenticationConverter implements Authentica
parserPool = registry.getParserPool();
}

private final Converter<OpenSaml4AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator = OpenSaml4AuthenticationProvider.createDefaultAssertionSignatureValidator();
private final Converter<OpenSaml5AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator = OpenSaml5AuthenticationProvider.createDefaultAssertionSignatureValidator();

private final Consumer<OpenSaml4AuthenticationProvider.AssertionToken> assertionElementsDecrypter = OpenSaml4AuthenticationProvider.createDefaultAssertionElementsDecrypter();
private final Consumer<OpenSaml5AuthenticationProvider.AssertionToken> assertionElementsDecrypter = OpenSaml5AuthenticationProvider.createDefaultAssertionElementsDecrypter();

private final Converter<OpenSaml4AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionValidator = createDefaultAssertionValidator();
private final Converter<OpenSaml5AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionValidator = createDefaultAssertionValidator();

private final Converter<OpenSaml4AuthenticationProvider.AssertionToken, AbstractAuthenticationToken> assertionTokenAuthenticationConverter = createDefaultAssertionAuthenticationConverter();
private final Converter<OpenSaml5AuthenticationProvider.AssertionToken, AbstractAuthenticationToken> assertionTokenAuthenticationConverter = createDefaultAssertionAuthenticationConverter();

private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
private final IdentityZoneManager identityZoneManager;
Expand All @@ -119,7 +119,7 @@ public Saml2BearerGrantAuthenticationConverter(RelyingPartyRegistrationResolver
*
* @return the default assertion validator strategy
*/
public static Converter<OpenSaml4AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator() {
public static Converter<OpenSaml5AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator() {

return createDefaultAssertionValidatorWithParameters(
params -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5)), true);
Expand All @@ -131,13 +131,13 @@ public static Converter<OpenSaml4AuthenticationProvider.AssertionToken, Saml2Res
*
* @return the default response authentication converter strategy
*/
static Converter<OpenSaml4AuthenticationProvider.AssertionToken, AbstractAuthenticationToken> createDefaultAssertionAuthenticationConverter() {
static Converter<OpenSaml5AuthenticationProvider.AssertionToken, AbstractAuthenticationToken> createDefaultAssertionAuthenticationConverter() {
return assertionToken -> {
Assertion assertion = assertionToken.getAssertion();
Saml2AuthenticationToken token = assertionToken.getToken();
String username = assertion.getSubject().getNameID().getValue();
Map<String, List<Object>> attributes = OpenSaml4AuthenticationProvider.getAssertionAttributes(assertion);
List<String> sessionIndexes = OpenSaml4AuthenticationProvider.getSessionIndexes(assertion);
Map<String, List<Object>> attributes = OpenSaml5AuthenticationProvider.getAssertionAttributes(assertion);
List<String> sessionIndexes = OpenSaml5AuthenticationProvider.getSessionIndexes(assertion);
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes,
sessionIndexes);
String registrationId = token.getRelyingPartyRegistration().getRegistrationId();
Expand Down Expand Up @@ -191,15 +191,15 @@ public Authentication authenticate(Authentication authentication) throws Authent
Assertion assertion = parseAssertion(serializedAssertion);
process(token, assertion);
AbstractAuthenticationToken authenticationResponse = this.assertionTokenAuthenticationConverter
.convert(new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token));
.convert(new OpenSaml5AuthenticationProvider.AssertionToken(assertion, token));
if (authenticationResponse != null) {
authenticationResponse.setDetails(authentication.getDetails());
}
return authenticationResponse;
} catch (Saml2AuthenticationException ex) {
throw ex;
} catch (Exception ex) {
throw OpenSaml4AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex);
throw OpenSaml5AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex);
}
}

Expand All @@ -210,7 +210,7 @@ private static Assertion parseAssertion(String assertion) throws Saml2Exception,
Element element = document.getDocumentElement();
return (Assertion) assertionUnmarshaller.unmarshall(element);
} catch (Exception ex) {
throw OpenSaml4AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INVALID_ASSERTION, "Unable to parse bearer assertion", ex);
throw OpenSaml5AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INVALID_ASSERTION, "Unable to parse bearer assertion", ex);
}
}

Expand All @@ -221,7 +221,7 @@ protected static Response parseSamlResponse(String samlResponse) throws Saml2Exc
Element element = document.getDocumentElement();
return (Response) responseUnMarshaller.unmarshall(element);
} catch (Exception ex) {
throw OpenSaml4AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INVALID_RESPONSE, "Unable to parse saml response", ex);
throw OpenSaml5AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INVALID_RESPONSE, "Unable to parse saml response", ex);
}
}

Expand All @@ -239,20 +239,20 @@ private void process(Saml2AuthenticationToken token, Assertion assertion) {
String issuer = getIssuer(assertion);
log.debug("Processing SAML response from {}", issuer);

OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token);
OpenSaml5AuthenticationProvider.AssertionToken assertionToken = new OpenSaml5AuthenticationProvider.AssertionToken(assertion, token);
Saml2ResponseValidatorResult result = this.assertionSignatureValidator.convert(assertionToken);
if (assertion.isSigned()) {
this.assertionElementsDecrypter.accept(new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token));
this.assertionElementsDecrypter.accept(new OpenSaml5AuthenticationProvider.AssertionToken(assertion, token));
} else {
throw OpenSaml4AuthenticationProvider.createAuthenticationException(
throw OpenSaml5AuthenticationProvider.createAuthenticationException(
Saml2ErrorCodes.INVALID_SIGNATURE,
"Assertion is missing a signature.",
null
);
}
result = result.concat(this.assertionValidator.convert(assertionToken));

if (!OpenSaml4AuthenticationProvider.hasName(assertion)) {
if (!OpenSaml5AuthenticationProvider.hasName(assertion)) {
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
"Assertion [" + assertion.getID() + "] is missing a subject");
result = result.concat(error);
Expand All @@ -266,7 +266,7 @@ private void process(Saml2AuthenticationToken token, Assertion assertion) {
log.debug("Found {} validation errors in SAML assertion [{}}]", errors.size(), assertion.getID());
Comment thread
strehle marked this conversation as resolved.
Outdated
}
Comment thread
strehle marked this conversation as resolved.
Outdated
Saml2Error first = errors.iterator().next();
throw OpenSaml4AuthenticationProvider.createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
throw OpenSaml5AuthenticationProvider.createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
} else {
log.debug("Successfully processed SAML Assertion [{}]", assertion.getID());
}
Expand Down
Loading
Loading