|
|
|
@ -21,13 +21,13 @@ import java.time.Duration; |
|
|
|
import java.time.Instant; |
|
|
|
import java.time.Instant; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.Collection; |
|
|
|
|
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.function.Consumer; |
|
|
|
|
|
|
|
import java.util.function.Function; |
|
|
|
import java.util.function.Function; |
|
|
|
import javax.annotation.Nonnull; |
|
|
|
import javax.annotation.Nonnull; |
|
|
|
|
|
|
|
|
|
|
|
@ -193,10 +193,12 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
|
|
|
|
|
|
|
|
private Converter<Saml2AuthenticationToken, SignatureTrustEngine> signatureTrustEngineConverter = |
|
|
|
private Converter<Saml2AuthenticationToken, SignatureTrustEngine> signatureTrustEngineConverter = |
|
|
|
new SignatureTrustEngineConverter(); |
|
|
|
new SignatureTrustEngineConverter(); |
|
|
|
private Converter<Saml2AuthenticationToken, SAML20AssertionValidator> assertionValidatorConverter = |
|
|
|
private Converter<Tuple, SAML20AssertionValidator> assertionValidatorConverter = |
|
|
|
new SAML20AssertionValidatorConverter(); |
|
|
|
new SAML20AssertionValidatorConverter(); |
|
|
|
private Converter<Saml2AuthenticationToken, ValidationContext> validationContextConverter = |
|
|
|
private Collection<ConditionValidator> conditionValidators = |
|
|
|
new ValidationContextConverter(params -> {}); |
|
|
|
Collections.singleton(new AudienceRestrictionConditionValidator()); |
|
|
|
|
|
|
|
private Converter<Tuple, ValidationContext> validationContextConverter = |
|
|
|
|
|
|
|
new ValidationContextConverter(); |
|
|
|
private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter(); |
|
|
|
private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter(); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -209,6 +211,33 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
this.parserPool = this.registry.getParserPool(); |
|
|
|
this.parserPool = this.registry.getParserPool(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set the the collection of {@link ConditionValidator}s used when validating an assertion. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param conditionValidators the collection of validators to use |
|
|
|
|
|
|
|
* @since 5.4 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setConditionValidators( |
|
|
|
|
|
|
|
Collection<ConditionValidator> conditionValidators) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Assert.notEmpty(conditionValidators, "conditionValidators cannot be empty"); |
|
|
|
|
|
|
|
this.conditionValidators = conditionValidators; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set the strategy for retrieving the {@link ValidationContext} used when |
|
|
|
|
|
|
|
* validating an assertion. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param validationContextConverter the strategy to use |
|
|
|
|
|
|
|
* @since 5.4 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setValidationContextConverter( |
|
|
|
|
|
|
|
Converter<Tuple, ValidationContext> validationContextConverter) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Assert.notNull(validationContextConverter, "validationContextConverter cannot be empty"); |
|
|
|
|
|
|
|
this.validationContextConverter = validationContextConverter; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Sets the {@link Converter} used for extracting assertion attributes that |
|
|
|
* Sets the {@link Converter} used for extracting assertion attributes that |
|
|
|
* can be mapped to authorities. |
|
|
|
* can be mapped to authorities. |
|
|
|
@ -238,8 +267,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setResponseTimeValidationSkew(Duration responseTimeValidationSkew) { |
|
|
|
public void setResponseTimeValidationSkew(Duration responseTimeValidationSkew) { |
|
|
|
this.responseTimeValidationSkew = responseTimeValidationSkew; |
|
|
|
this.responseTimeValidationSkew = responseTimeValidationSkew; |
|
|
|
this.validationContextConverter = new ValidationContextConverter( |
|
|
|
|
|
|
|
params -> params.put(CLOCK_SKEW, responseTimeValidationSkew.toMillis())); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -303,7 +330,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
throw authException(INVALID_SIGNATURE, "Either the response or one of the assertions is unsigned. " + |
|
|
|
throw authException(INVALID_SIGNATURE, "Either the response or one of the assertions is unsigned. " + |
|
|
|
"Please either sign the response or all of the assertions."); |
|
|
|
"Please either sign the response or all of the assertions."); |
|
|
|
} |
|
|
|
} |
|
|
|
validationExceptions.putAll(validateAssertions(token, assertions)); |
|
|
|
validationExceptions.putAll(validateAssertions(token, response)); |
|
|
|
|
|
|
|
|
|
|
|
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); |
|
|
|
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); |
|
|
|
NameID nameId = decryptPrincipal(decrypter, firstAssertion); |
|
|
|
NameID nameId = decryptPrincipal(decrypter, firstAssertion); |
|
|
|
@ -392,7 +419,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Map<String, Saml2AuthenticationException> validateAssertions |
|
|
|
private Map<String, Saml2AuthenticationException> validateAssertions |
|
|
|
(Saml2AuthenticationToken token, List<Assertion> assertions) { |
|
|
|
(Saml2AuthenticationToken token, Response response) { |
|
|
|
|
|
|
|
List<Assertion> assertions = response.getAssertions(); |
|
|
|
if (assertions.isEmpty()) { |
|
|
|
if (assertions.isEmpty()) { |
|
|
|
throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response."); |
|
|
|
throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response."); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -401,14 +429,16 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Validating " + assertions.size() + " assertions"); |
|
|
|
logger.debug("Validating " + assertions.size() + " assertions"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Tuple tuple = new Tuple(token, response); |
|
|
|
|
|
|
|
SAML20AssertionValidator validator = this.assertionValidatorConverter.convert(tuple); |
|
|
|
|
|
|
|
ValidationContext context = this.validationContextConverter.convert(tuple); |
|
|
|
for (Assertion assertion : assertions) { |
|
|
|
for (Assertion assertion : assertions) { |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
logger.trace("Validating assertion " + assertion.getID()); |
|
|
|
logger.trace("Validating assertion " + assertion.getID()); |
|
|
|
} |
|
|
|
} |
|
|
|
try { |
|
|
|
try { |
|
|
|
ValidationContext context = this.validationContextConverter.convert(token); |
|
|
|
if (validator.validate(assertion, context) != ValidationResult.VALID) { |
|
|
|
ValidationResult result = this.assertionValidatorConverter.convert(token).validate(assertion, context); |
|
|
|
|
|
|
|
if (result != ValidationResult.VALID) { |
|
|
|
|
|
|
|
String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", |
|
|
|
String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", |
|
|
|
assertion.getID(), ((Response) assertion.getParent()).getID(), |
|
|
|
assertion.getID(), ((Response) assertion.getParent()).getID(), |
|
|
|
context.getValidationFailureMessage()); |
|
|
|
context.getValidationFailureMessage()); |
|
|
|
@ -512,6 +542,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static class SignatureTrustEngineConverter implements Converter<Saml2AuthenticationToken, SignatureTrustEngine> { |
|
|
|
private static class SignatureTrustEngineConverter implements Converter<Saml2AuthenticationToken, SignatureTrustEngine> { |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public SignatureTrustEngine convert(Saml2AuthenticationToken token) { |
|
|
|
public SignatureTrustEngine convert(Saml2AuthenticationToken token) { |
|
|
|
Set<Credential> credentials = new HashSet<>(); |
|
|
|
Set<Credential> credentials = new HashSet<>(); |
|
|
|
@ -530,35 +561,27 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static class ValidationContextConverter implements Converter<Saml2AuthenticationToken, ValidationContext> { |
|
|
|
private class ValidationContextConverter implements Converter<Tuple, ValidationContext> { |
|
|
|
Consumer<Map<String, Object>> validationContextParametersConverter; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ValidationContextConverter(Consumer<Map<String, Object>> validationContextParametersConverter) { |
|
|
|
|
|
|
|
this.validationContextParametersConverter = validationContextParametersConverter; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public ValidationContext convert(Saml2AuthenticationToken token) { |
|
|
|
public ValidationContext convert(Tuple tuple) { |
|
|
|
String audience = token.getRelyingPartyRegistration().getEntityId(); |
|
|
|
String audience = tuple.authentication.getRelyingPartyRegistration().getEntityId(); |
|
|
|
String recipient = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); |
|
|
|
String recipient = tuple.authentication.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); |
|
|
|
Map<String, Object> params = new HashMap<>(); |
|
|
|
Map<String, Object> params = new HashMap<>(); |
|
|
|
params.put(CLOCK_SKEW, Duration.ofMinutes(5).toMillis()); |
|
|
|
params.put(CLOCK_SKEW, OpenSamlAuthenticationProvider.this.responseTimeValidationSkew.toMillis()); |
|
|
|
params.put(COND_VALID_AUDIENCES, singleton(audience)); |
|
|
|
params.put(COND_VALID_AUDIENCES, singleton(audience)); |
|
|
|
params.put(SC_VALID_RECIPIENTS, singleton(recipient)); |
|
|
|
params.put(SC_VALID_RECIPIENTS, singleton(recipient)); |
|
|
|
params.put(SIGNATURE_REQUIRED, false); // this verification is performed earlier
|
|
|
|
params.put(SIGNATURE_REQUIRED, false); // this verification is performed earlier
|
|
|
|
this.validationContextParametersConverter.accept(params); |
|
|
|
|
|
|
|
return new ValidationContext(params); |
|
|
|
return new ValidationContext(params); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private class SAML20AssertionValidatorConverter implements Converter<Saml2AuthenticationToken, SAML20AssertionValidator> { |
|
|
|
private class SAML20AssertionValidatorConverter implements Converter<Tuple, SAML20AssertionValidator> { |
|
|
|
private final Collection<ConditionValidator> conditions = new ArrayList<>(); |
|
|
|
|
|
|
|
private final Collection<SubjectConfirmationValidator> subjects = new ArrayList<>(); |
|
|
|
private final Collection<SubjectConfirmationValidator> subjects = new ArrayList<>(); |
|
|
|
private final Collection<StatementValidator> statements = new ArrayList<>(); |
|
|
|
private final Collection<StatementValidator> statements = new ArrayList<>(); |
|
|
|
private final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); |
|
|
|
private final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); |
|
|
|
|
|
|
|
|
|
|
|
SAML20AssertionValidatorConverter() { |
|
|
|
SAML20AssertionValidatorConverter() { |
|
|
|
this.conditions.add(new AudienceRestrictionConditionValidator()); |
|
|
|
|
|
|
|
this.subjects.add(new BearerSubjectConfirmationValidator() { |
|
|
|
this.subjects.add(new BearerSubjectConfirmationValidator() { |
|
|
|
@Nonnull |
|
|
|
@Nonnull |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -571,9 +594,11 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public SAML20AssertionValidator convert(Saml2AuthenticationToken token) { |
|
|
|
public SAML20AssertionValidator convert(Tuple tuple) { |
|
|
|
return new SAML20AssertionValidator(this.conditions, this.subjects, this.statements, |
|
|
|
Collection<ConditionValidator> conditions = |
|
|
|
OpenSamlAuthenticationProvider.this.signatureTrustEngineConverter.convert(token), |
|
|
|
OpenSamlAuthenticationProvider.this.conditionValidators; |
|
|
|
|
|
|
|
return new SAML20AssertionValidator(conditions, this.subjects, this.statements, |
|
|
|
|
|
|
|
OpenSamlAuthenticationProvider.this.signatureTrustEngineConverter.convert(tuple.authentication), |
|
|
|
this.validator); |
|
|
|
this.validator); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -616,4 +641,27 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
|
|
|
|
|
|
|
|
return new Saml2AuthenticationException(validationError(code, description), cause); |
|
|
|
return new Saml2AuthenticationException(validationError(code, description), cause); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* A tuple containing the authentication token and the associated OpenSAML {@link Response}. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @since 5.4 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public static class Tuple { |
|
|
|
|
|
|
|
private final Saml2AuthenticationToken authentication; |
|
|
|
|
|
|
|
private final Response response; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Tuple(Saml2AuthenticationToken authentication, Response response) { |
|
|
|
|
|
|
|
this.authentication = authentication; |
|
|
|
|
|
|
|
this.response = response; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Saml2AuthenticationToken getAuthentication() { |
|
|
|
|
|
|
|
return this.authentication; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Response getResponse() { |
|
|
|
|
|
|
|
return this.response; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|