25 changed files with 14 additions and 2273 deletions
2
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
2
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java
0
saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java → saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java
@ -1,864 +0,0 @@
@@ -1,864 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2021 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.time.Duration; |
||||
import java.time.Instant; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import javax.annotation.Nonnull; |
||||
import javax.xml.namespace.QName; |
||||
|
||||
import net.shibboleth.utilities.java.support.xml.ParserPool; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.opensaml.core.config.ConfigurationService; |
||||
import org.opensaml.core.xml.XMLObject; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry; |
||||
import org.opensaml.core.xml.schema.XSAny; |
||||
import org.opensaml.core.xml.schema.XSBoolean; |
||||
import org.opensaml.core.xml.schema.XSBooleanValue; |
||||
import org.opensaml.core.xml.schema.XSDateTime; |
||||
import org.opensaml.core.xml.schema.XSInteger; |
||||
import org.opensaml.core.xml.schema.XSString; |
||||
import org.opensaml.core.xml.schema.XSURI; |
||||
import org.opensaml.saml.common.assertion.AssertionValidationException; |
||||
import org.opensaml.saml.common.assertion.ValidationContext; |
||||
import org.opensaml.saml.common.assertion.ValidationResult; |
||||
import org.opensaml.saml.saml2.assertion.ConditionValidator; |
||||
import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator; |
||||
import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; |
||||
import org.opensaml.saml.saml2.assertion.StatementValidator; |
||||
import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator; |
||||
import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator; |
||||
import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator; |
||||
import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator; |
||||
import org.opensaml.saml.saml2.core.Assertion; |
||||
import org.opensaml.saml.saml2.core.Attribute; |
||||
import org.opensaml.saml.saml2.core.AttributeStatement; |
||||
import org.opensaml.saml.saml2.core.Condition; |
||||
import org.opensaml.saml.saml2.core.EncryptedAssertion; |
||||
import org.opensaml.saml.saml2.core.OneTimeUse; |
||||
import org.opensaml.saml.saml2.core.Response; |
||||
import org.opensaml.saml.saml2.core.StatusCode; |
||||
import org.opensaml.saml.saml2.core.SubjectConfirmation; |
||||
import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; |
||||
import org.opensaml.saml.saml2.encryption.Decrypter; |
||||
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; |
||||
import org.opensaml.xmlsec.signature.support.SignaturePrevalidator; |
||||
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; |
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.core.log.LogMessage; |
||||
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
import org.springframework.security.authentication.AuthenticationProvider; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; |
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService; |
||||
import org.springframework.security.saml2.core.Saml2Error; |
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes; |
||||
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Implementation of {@link AuthenticationProvider} for SAML authentications when |
||||
* receiving a {@code Response} object containing an {@code Assertion}. This |
||||
* implementation uses the {@code OpenSAML 3} library. |
||||
* |
||||
* <p> |
||||
* The {@link OpenSamlAuthenticationProvider} supports {@link Saml2AuthenticationToken} |
||||
* objects that contain a SAML response in its decoded XML format |
||||
* {@link Saml2AuthenticationToken#getSaml2Response()} along with the information about |
||||
* the asserting party, the identity provider (IDP), as well as the relying party, the |
||||
* service provider (SP, this application). |
||||
* <p> |
||||
* The {@link Saml2AuthenticationToken} will be processed into a SAML Response object. The |
||||
* SAML response object can be signed. If the Response is signed, a signature will not be |
||||
* required on the assertion. |
||||
* <p> |
||||
* While a response object can contain a list of assertion, this provider will only |
||||
* leverage the first valid assertion for the purpose of authentication. Assertions that |
||||
* do not pass validation will be ignored. If no valid assertions are found a |
||||
* {@link Saml2AuthenticationException} is thrown. |
||||
* <p> |
||||
* This provider supports two types of encrypted SAML elements |
||||
* <ul> |
||||
* <li><a href= |
||||
* "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=17">EncryptedAssertion</a></li> |
||||
* <li><a href= |
||||
* "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=14">EncryptedID</a></li> |
||||
* </ul> |
||||
* If the assertion is encrypted, then signature validation on the assertion is no longer |
||||
* required. |
||||
* <p> |
||||
* This provider does not perform an X509 certificate validation on the configured |
||||
* asserting party, IDP, verification certificates. |
||||
* |
||||
* @author Ryan Cassar |
||||
* @since 5.2 |
||||
* @see <a href= |
||||
* "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2 |
||||
* StatusResponse</a> |
||||
* @see <a href="https://wiki.shibboleth.net/confluence/display/OS30/Home">OpenSAML 3</a> |
||||
* @deprecated Because OpenSAML 3 has reached End-of-Life, please update to |
||||
* {@code OpenSaml4AuthenticationProvider} |
||||
*/ |
||||
public final class OpenSamlAuthenticationProvider implements AuthenticationProvider { |
||||
|
||||
static { |
||||
OpenSamlInitializationService.initialize(); |
||||
} |
||||
|
||||
private static Log logger = LogFactory.getLog(OpenSamlAuthenticationProvider.class); |
||||
|
||||
private final XMLObjectProviderRegistry registry; |
||||
|
||||
private final ResponseUnmarshaller responseUnmarshaller; |
||||
|
||||
private final ParserPool parserPool; |
||||
|
||||
private Converter<Assertion, Collection<? extends GrantedAuthority>> authoritiesExtractor = ((a) -> Collections |
||||
.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); |
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = ((a) -> a); |
||||
|
||||
private Duration responseTimeValidationSkew = Duration.ofMinutes(5); |
||||
|
||||
private Converter<ResponseToken, Saml2ResponseValidatorResult> responseSignatureValidator = createDefaultResponseSignatureValidator(); |
||||
|
||||
private Consumer<ResponseToken> responseElementsDecrypter = createDefaultResponseElementsDecrypter(); |
||||
|
||||
private Converter<ResponseToken, Saml2ResponseValidatorResult> responseValidator = createDefaultResponseValidator(); |
||||
|
||||
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator = createDefaultAssertionSignatureValidator(); |
||||
|
||||
private Consumer<AssertionToken> assertionElementsDecrypter = createDefaultAssertionElementsDecrypter(); |
||||
|
||||
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator = createCompatibleAssertionValidator(); |
||||
|
||||
private Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter = createCompatibleResponseAuthenticationConverter(); |
||||
|
||||
/** |
||||
* Creates an {@link OpenSamlAuthenticationProvider} |
||||
*/ |
||||
public OpenSamlAuthenticationProvider() { |
||||
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class); |
||||
this.responseUnmarshaller = (ResponseUnmarshaller) this.registry.getUnmarshallerFactory() |
||||
.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); |
||||
this.parserPool = this.registry.getParserPool(); |
||||
} |
||||
|
||||
/** |
||||
* Set the {@link Consumer} strategy to use for decrypting elements of a validated |
||||
* {@link Response}. The default strategy decrypts all {@link EncryptedAssertion}s |
||||
* using OpenSAML's {@link Decrypter}, adding the results to |
||||
* {@link Response#getAssertions()}. |
||||
* |
||||
* You can use this method to configure the {@link Decrypter} instance like so: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* provider.setResponseElementsDecrypter((responseToken) -> { |
||||
* DecrypterParameters parameters = new DecrypterParameters(); |
||||
* // ... set parameters as needed
|
||||
* Decrypter decrypter = new Decrypter(parameters); |
||||
* Response response = responseToken.getResponse(); |
||||
* EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0); |
||||
* try { |
||||
* Assertion assertion = decrypter.decrypt(encrypted); |
||||
* response.getAssertions().add(assertion); |
||||
* } catch (Exception e) { |
||||
* throw new Saml2AuthenticationException(...); |
||||
* } |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* Or, in the event that you have your own custom decryption interface, the same |
||||
* pattern applies: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* Converter<EncryptedAssertion, Assertion> myService = ... |
||||
* provider.setResponseDecrypter((responseToken) -> { |
||||
* Response response = responseToken.getResponse(); |
||||
* response.getEncryptedAssertions().stream() |
||||
* .map(service::decrypt).forEach(response.getAssertions()::add); |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* This is valuable when using an external service to perform the decryption. |
||||
* @param responseElementsDecrypter the {@link Consumer} for decrypting response |
||||
* elements |
||||
* @since 5.5 |
||||
*/ |
||||
public void setResponseElementsDecrypter(Consumer<ResponseToken> responseElementsDecrypter) { |
||||
Assert.notNull(responseElementsDecrypter, "responseElementsDecrypter cannot be null"); |
||||
this.responseElementsDecrypter = responseElementsDecrypter; |
||||
} |
||||
|
||||
/** |
||||
* Set the {@link Converter} to use for validating each {@link Assertion} in the SAML |
||||
* 2.0 Response. |
||||
* |
||||
* You can still invoke the default validator by delgating to |
||||
* {@link #createAssertionValidator}, like so: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* provider.setAssertionValidator(assertionToken -> { |
||||
* Saml2ResponseValidatorResult result = createDefaultAssertionValidator() |
||||
* .convert(assertionToken) |
||||
* return result.concat(myCustomValidator.convert(assertionToken)); |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* You can also use this method to configure the provider to use a different |
||||
* {@link ValidationContext} from the default, like so: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* provider.setAssertionValidator( |
||||
* createDefaultAssertionValidator(assertionToken -> { |
||||
* Map<String, Object> params = new HashMap<>(); |
||||
* params.put(CLOCK_SKEW, 2 * 60 * 1000); |
||||
* // other parameters
|
||||
* return new ValidationContext(params); |
||||
* })); |
||||
* </pre> |
||||
* |
||||
* Consider taking a look at {@link #createValidationContext} to see how it constructs |
||||
* a {@link ValidationContext}. |
||||
* |
||||
* It is not necessary to delegate to the default validator. You can safely replace it |
||||
* entirely with your own. Note that signature verification is performed as a separate |
||||
* step from this validator. |
||||
* |
||||
* This method takes precedence over {@link #setResponseTimeValidationSkew}. |
||||
* @param assertionValidator the strategy for validating a given assertion |
||||
* @since 5.4 |
||||
*/ |
||||
public void setAssertionValidator(Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator) { |
||||
Assert.notNull(assertionValidator, "assertionValidator cannot be null"); |
||||
this.assertionValidator = assertionValidator; |
||||
} |
||||
|
||||
/** |
||||
* Set the {@link Consumer} strategy to use for decrypting elements of a validated |
||||
* {@link Assertion}. |
||||
* |
||||
* You can use this method to configure the {@link Decrypter} used like so: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* provider.setResponseDecrypter((assertionToken) -> { |
||||
* DecrypterParameters parameters = new DecrypterParameters(); |
||||
* // ... set parameters as needed
|
||||
* Decrypter decrypter = new Decrypter(parameters); |
||||
* Assertion assertion = assertionToken.getAssertion(); |
||||
* EncryptedID encrypted = assertion.getSubject().getEncryptedID(); |
||||
* try { |
||||
* NameID name = decrypter.decrypt(encrypted); |
||||
* assertion.getSubject().setNameID(name); |
||||
* } catch (Exception e) { |
||||
* throw new Saml2AuthenticationException(...); |
||||
* } |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* Or, in the event that you have your own custom interface, the same pattern applies: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* MyDecryptionService myService = ... |
||||
* provider.setResponseDecrypter((responseToken) -> { |
||||
* Assertion assertion = assertionToken.getAssertion(); |
||||
* EncryptedID encrypted = assertion.getSubject().getEncryptedID(); |
||||
* NameID name = myService.decrypt(encrypted); |
||||
* assertion.getSubject().setNameID(name); |
||||
* }); |
||||
* </pre> |
||||
* @param assertionDecrypter the {@link Consumer} for decrypting assertion elements |
||||
* @since 5.5 |
||||
*/ |
||||
public void setAssertionElementsDecrypter(Consumer<AssertionToken> assertionDecrypter) { |
||||
Assert.notNull(assertionDecrypter, "assertionDecrypter cannot be null"); |
||||
this.assertionElementsDecrypter = assertionDecrypter; |
||||
} |
||||
|
||||
/** |
||||
* Set the {@link Converter} to use for converting a validated {@link Response} into |
||||
* an {@link AbstractAuthenticationToken}. |
||||
* |
||||
* You can delegate to the default behavior by calling |
||||
* {@link #createDefaultResponseAuthenticationConverter()} like so: |
||||
* |
||||
* <pre> |
||||
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
* Converter<ResponseToken, Saml2Authentication> authenticationConverter = |
||||
* createDefaultResponseAuthenticationConverter(); |
||||
* provider.setResponseAuthenticationConverter(responseToken -> { |
||||
* Saml2Authentication authentication = authenticationConverter.convert(responseToken); |
||||
* User user = myUserRepository.findByUsername(authentication.getName()); |
||||
* return new MyAuthentication(authentication, user); |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* This method takes precedence over {@link #setAuthoritiesExtractor(Converter)} and |
||||
* {@link #setAuthoritiesMapper(GrantedAuthoritiesMapper)}. |
||||
* @param responseAuthenticationConverter the {@link Converter} to use |
||||
* @since 5.4 |
||||
*/ |
||||
public void setResponseAuthenticationConverter( |
||||
Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter) { |
||||
Assert.notNull(responseAuthenticationConverter, "responseAuthenticationConverter cannot be null"); |
||||
this.responseAuthenticationConverter = responseAuthenticationConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for extracting assertion attributes that can be |
||||
* mapped to authorities. |
||||
* @param authoritiesExtractor the {@code Converter} used for mapping the assertion |
||||
* attributes to authorities |
||||
* @deprecated Use {@link #setResponseAuthenticationConverter(Converter)} instead |
||||
*/ |
||||
public void setAuthoritiesExtractor( |
||||
Converter<Assertion, Collection<? extends GrantedAuthority>> authoritiesExtractor) { |
||||
Assert.notNull(authoritiesExtractor, "authoritiesExtractor cannot be null"); |
||||
this.authoritiesExtractor = authoritiesExtractor; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping assertion attributes to |
||||
* a new set of authorities which will be associated to the |
||||
* {@link Saml2Authentication}. Note: This implementation is only retrieving |
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the |
||||
* user's authorities |
||||
* @deprecated Use {@link #setResponseAuthenticationConverter(Converter)} instead |
||||
*/ |
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { |
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null"); |
||||
this.authoritiesMapper = authoritiesMapper; |
||||
} |
||||
|
||||
/** |
||||
* Sets the duration for how much time skew an assertion may tolerate during |
||||
* timestamp, NotOnOrBefore and NotOnOrAfter, validation. |
||||
* @param responseTimeValidationSkew duration for skew tolerance |
||||
* @deprecated Use {@link #setAssertionValidator(Converter)} instead |
||||
*/ |
||||
public void setResponseTimeValidationSkew(Duration responseTimeValidationSkew) { |
||||
this.responseTimeValidationSkew = responseTimeValidationSkew; |
||||
} |
||||
|
||||
/** |
||||
* Construct a default strategy for validating each SAML 2.0 Assertion and associated |
||||
* {@link Authentication} token |
||||
* @return the default assertion validator strategy |
||||
* @since 5.4 |
||||
*/ |
||||
public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator() { |
||||
|
||||
return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, |
||||
(assertionToken) -> SAML20AssertionValidators.attributeValidator, |
||||
(assertionToken) -> createValidationContext(assertionToken, (params) -> { |
||||
})); |
||||
} |
||||
|
||||
/** |
||||
* Construct a default strategy for validating each SAML 2.0 Assertion and associated |
||||
* {@link Authentication} token |
||||
* @param contextConverter the conversion strategy to use to generate a |
||||
* {@link ValidationContext} for each assertion being validated |
||||
* @return the default assertion validator strategy |
||||
* @since 5.4 |
||||
*/ |
||||
public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator( |
||||
Converter<AssertionToken, ValidationContext> contextConverter) { |
||||
|
||||
return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, |
||||
(assertionToken) -> SAML20AssertionValidators.attributeValidator, contextConverter); |
||||
} |
||||
|
||||
/** |
||||
* Construct a default strategy for converting a SAML 2.0 Response and |
||||
* {@link Authentication} token into a {@link Saml2Authentication} |
||||
* @return the default response authentication converter strategy |
||||
* @since 5.4 |
||||
*/ |
||||
public static Converter<ResponseToken, Saml2Authentication> createDefaultResponseAuthenticationConverter() { |
||||
return (responseToken) -> { |
||||
Saml2AuthenticationToken token = responseToken.token; |
||||
Response response = responseToken.response; |
||||
Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); |
||||
String username = assertion.getSubject().getNameID().getValue(); |
||||
Map<String, List<Object>> attributes = getAssertionAttributes(assertion); |
||||
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes); |
||||
String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); |
||||
principal.setRelyingPartyRegistrationId(registrationId); |
||||
return new Saml2Authentication(principal, token.getSaml2Response(), |
||||
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* @param authentication the authentication request object, must be of type |
||||
* {@link Saml2AuthenticationToken} |
||||
* @return {@link Saml2Authentication} if the assertion is valid |
||||
* @throws AuthenticationException if a validation exception occurs |
||||
*/ |
||||
@Override |
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
||||
try { |
||||
Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication; |
||||
String serializedResponse = token.getSaml2Response(); |
||||
Response response = parse(serializedResponse); |
||||
process(token, response); |
||||
return this.responseAuthenticationConverter.convert(new ResponseToken(response, token)); |
||||
} |
||||
catch (Saml2AuthenticationException ex) { |
||||
throw ex; |
||||
} |
||||
catch (Exception ex) { |
||||
throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(Class<?> authentication) { |
||||
return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication); |
||||
} |
||||
|
||||
private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException { |
||||
try { |
||||
Document document = this.parserPool |
||||
.parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); |
||||
Element element = document.getDocumentElement(); |
||||
return (Response) this.responseUnmarshaller.unmarshall(element); |
||||
} |
||||
catch (Exception ex) { |
||||
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
private void process(Saml2AuthenticationToken token, Response response) { |
||||
String issuer = response.getIssuer().getValue(); |
||||
logger.debug(LogMessage.format("Processing SAML response from %s", issuer)); |
||||
boolean responseSigned = response.isSigned(); |
||||
|
||||
ResponseToken responseToken = new ResponseToken(response, token); |
||||
Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken); |
||||
if (responseSigned) { |
||||
this.responseElementsDecrypter.accept(responseToken); |
||||
} |
||||
result = result.concat(this.responseValidator.convert(responseToken)); |
||||
boolean allAssertionsSigned = true; |
||||
for (Assertion assertion : response.getAssertions()) { |
||||
AssertionToken assertionToken = new AssertionToken(assertion, token); |
||||
result = result.concat(this.assertionSignatureValidator.convert(assertionToken)); |
||||
allAssertionsSigned = allAssertionsSigned && assertion.isSigned(); |
||||
if (responseSigned || assertion.isSigned()) { |
||||
this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token)); |
||||
} |
||||
result = result.concat(this.assertionValidator.convert(assertionToken)); |
||||
} |
||||
if (!responseSigned && !allAssertionsSigned) { |
||||
String description = "Either the response or one of the assertions is unsigned. " |
||||
+ "Please either sign the response or all of the assertions."; |
||||
throw createAuthenticationException(Saml2ErrorCodes.INVALID_SIGNATURE, description, null); |
||||
} |
||||
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); |
||||
if (!hasName(firstAssertion)) { |
||||
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, |
||||
"Assertion [" + firstAssertion.getID() + "] is missing a subject"); |
||||
result = result.concat(error); |
||||
} |
||||
|
||||
if (result.hasErrors()) { |
||||
Collection<Saml2Error> errors = result.getErrors(); |
||||
if (logger.isTraceEnabled()) { |
||||
logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() |
||||
+ "]: " + errors); |
||||
} |
||||
else if (logger.isDebugEnabled()) { |
||||
logger.debug( |
||||
"Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]"); |
||||
} |
||||
Saml2Error first = errors.iterator().next(); |
||||
throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null); |
||||
} |
||||
else { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Successfully processed SAML Response [" + response.getID() + "]"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseSignatureValidator() { |
||||
return (responseToken) -> { |
||||
Response response = responseToken.getResponse(); |
||||
RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); |
||||
if (response.isSigned()) { |
||||
return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature()); |
||||
} |
||||
return Saml2ResponseValidatorResult.success(); |
||||
}; |
||||
} |
||||
|
||||
private Consumer<ResponseToken> createDefaultResponseElementsDecrypter() { |
||||
return (responseToken) -> { |
||||
Response response = responseToken.getResponse(); |
||||
RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); |
||||
try { |
||||
OpenSamlDecryptionUtils.decryptResponseElements(response, registration); |
||||
} |
||||
catch (Saml2Exception ex) { |
||||
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseValidator() { |
||||
return (responseToken) -> { |
||||
Response response = responseToken.getResponse(); |
||||
Saml2AuthenticationToken token = responseToken.getToken(); |
||||
Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success(); |
||||
String statusCode = getStatusCode(response); |
||||
if (!StatusCode.SUCCESS.equals(statusCode)) { |
||||
String message = String.format("Invalid status [%s] for SAML response [%s]", statusCode, |
||||
response.getID()); |
||||
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, message)); |
||||
} |
||||
String issuer = response.getIssuer().getValue(); |
||||
String destination = response.getDestination(); |
||||
String location = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); |
||||
if (StringUtils.hasText(destination) && !destination.equals(location)) { |
||||
String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID() |
||||
+ "]"; |
||||
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message)); |
||||
} |
||||
String assertingPartyEntityId = token.getRelyingPartyRegistration().getAssertingPartyDetails() |
||||
.getEntityId(); |
||||
if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) { |
||||
String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID()); |
||||
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message)); |
||||
} |
||||
if (response.getAssertions().isEmpty()) { |
||||
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, |
||||
"No assertions found in response.", null); |
||||
} |
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
private String getStatusCode(Response response) { |
||||
if (response.getStatus() == null) { |
||||
return StatusCode.SUCCESS; |
||||
} |
||||
if (response.getStatus().getStatusCode() == null) { |
||||
return StatusCode.SUCCESS; |
||||
} |
||||
return response.getStatus().getStatusCode().getValue(); |
||||
} |
||||
|
||||
private Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionSignatureValidator() { |
||||
return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> { |
||||
RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); |
||||
SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration); |
||||
return SAML20AssertionValidators.createSignatureValidator(engine); |
||||
}, (assertionToken) -> new ValidationContext( |
||||
Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false))); |
||||
} |
||||
|
||||
private Consumer<AssertionToken> createDefaultAssertionElementsDecrypter() { |
||||
return (assertionToken) -> { |
||||
Assertion assertion = assertionToken.getAssertion(); |
||||
RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); |
||||
try { |
||||
OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration); |
||||
} |
||||
catch (Saml2Exception ex) { |
||||
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private Converter<AssertionToken, Saml2ResponseValidatorResult> createCompatibleAssertionValidator() { |
||||
return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, |
||||
(assertionToken) -> SAML20AssertionValidators.attributeValidator, |
||||
(assertionToken) -> createValidationContext(assertionToken, |
||||
(params) -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, |
||||
this.responseTimeValidationSkew.toMillis()))); |
||||
} |
||||
|
||||
private Converter<ResponseToken, Saml2Authentication> createCompatibleResponseAuthenticationConverter() { |
||||
return (responseToken) -> { |
||||
Response response = responseToken.response; |
||||
Saml2AuthenticationToken token = responseToken.token; |
||||
Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); |
||||
String username = assertion.getSubject().getNameID().getValue(); |
||||
Map<String, List<Object>> attributes = getAssertionAttributes(assertion); |
||||
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes); |
||||
String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); |
||||
principal.setRelyingPartyRegistrationId(registrationId); |
||||
return new Saml2Authentication(principal, token.getSaml2Response(), |
||||
this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion))); |
||||
}; |
||||
} |
||||
|
||||
private Collection<? extends GrantedAuthority> getAssertionAuthorities(Assertion assertion) { |
||||
return this.authoritiesExtractor.convert(assertion); |
||||
} |
||||
|
||||
private boolean hasName(Assertion assertion) { |
||||
if (assertion == null) { |
||||
return false; |
||||
} |
||||
if (assertion.getSubject() == null) { |
||||
return false; |
||||
} |
||||
if (assertion.getSubject().getNameID() == null) { |
||||
return false; |
||||
} |
||||
return assertion.getSubject().getNameID().getValue() != null; |
||||
} |
||||
|
||||
private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) { |
||||
Map<String, List<Object>> attributeMap = new LinkedHashMap<>(); |
||||
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) { |
||||
for (Attribute attribute : attributeStatement.getAttributes()) { |
||||
List<Object> attributeValues = new ArrayList<>(); |
||||
for (XMLObject xmlObject : attribute.getAttributeValues()) { |
||||
Object attributeValue = getXmlObjectValue(xmlObject); |
||||
if (attributeValue != null) { |
||||
attributeValues.add(attributeValue); |
||||
} |
||||
} |
||||
attributeMap.put(attribute.getName(), attributeValues); |
||||
} |
||||
} |
||||
return attributeMap; |
||||
} |
||||
|
||||
private static Object getXmlObjectValue(XMLObject xmlObject) { |
||||
if (xmlObject instanceof XSAny) { |
||||
return ((XSAny) xmlObject).getTextContent(); |
||||
} |
||||
if (xmlObject instanceof XSString) { |
||||
return ((XSString) xmlObject).getValue(); |
||||
} |
||||
if (xmlObject instanceof XSInteger) { |
||||
return ((XSInteger) xmlObject).getValue(); |
||||
} |
||||
if (xmlObject instanceof XSURI) { |
||||
return ((XSURI) xmlObject).getValue(); |
||||
} |
||||
if (xmlObject instanceof XSBoolean) { |
||||
XSBooleanValue xsBooleanValue = ((XSBoolean) xmlObject).getValue(); |
||||
return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null; |
||||
} |
||||
if (xmlObject instanceof XSDateTime) { |
||||
return Instant.ofEpochMilli(((XSDateTime) xmlObject).getValue().getMillis()); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static Saml2AuthenticationException createAuthenticationException(String code, String message, |
||||
Exception cause) { |
||||
return new Saml2AuthenticationException(new Saml2Error(code, message), cause); |
||||
} |
||||
|
||||
private static Converter<AssertionToken, Saml2ResponseValidatorResult> createAssertionValidator(String errorCode, |
||||
Converter<AssertionToken, SAML20AssertionValidator> validatorConverter, |
||||
Converter<AssertionToken, ValidationContext> contextConverter) { |
||||
|
||||
return (assertionToken) -> { |
||||
Assertion assertion = assertionToken.assertion; |
||||
SAML20AssertionValidator validator = validatorConverter.convert(assertionToken); |
||||
ValidationContext context = contextConverter.convert(assertionToken); |
||||
try { |
||||
ValidationResult result = validator.validate(assertion, context); |
||||
if (result == ValidationResult.VALID) { |
||||
return Saml2ResponseValidatorResult.success(); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), |
||||
((Response) assertion.getParent()).getID(), ex.getMessage()); |
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); |
||||
} |
||||
String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), |
||||
((Response) assertion.getParent()).getID(), context.getValidationFailureMessage()); |
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); |
||||
}; |
||||
} |
||||
|
||||
private static ValidationContext createValidationContext(AssertionToken assertionToken, |
||||
Consumer<Map<String, Object>> paramsConsumer) { |
||||
String audience = assertionToken.token.getRelyingPartyRegistration().getEntityId(); |
||||
String recipient = assertionToken.token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); |
||||
Map<String, Object> params = new HashMap<>(); |
||||
params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience)); |
||||
params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(recipient)); |
||||
paramsConsumer.accept(params); |
||||
return new ValidationContext(params); |
||||
} |
||||
|
||||
private static class SAML20AssertionValidators { |
||||
|
||||
private static final Collection<ConditionValidator> conditions = new ArrayList<>(); |
||||
|
||||
private static final Collection<SubjectConfirmationValidator> subjects = new ArrayList<>(); |
||||
|
||||
private static final Collection<StatementValidator> statements = new ArrayList<>(); |
||||
|
||||
private static final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); |
||||
|
||||
static { |
||||
conditions.add(new AudienceRestrictionConditionValidator()); |
||||
conditions.add(new DelegationRestrictionConditionValidator()); |
||||
conditions.add(new ConditionValidator() { |
||||
@Nonnull |
||||
@Override |
||||
public QName getServicedCondition() { |
||||
return OneTimeUse.DEFAULT_ELEMENT_NAME; |
||||
} |
||||
|
||||
@Nonnull |
||||
@Override |
||||
public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { |
||||
// applications should validate their own OneTimeUse conditions
|
||||
return ValidationResult.VALID; |
||||
} |
||||
}); |
||||
subjects.add(new BearerSubjectConfirmationValidator() { |
||||
@Override |
||||
protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion, |
||||
ValidationContext context) throws AssertionValidationException { |
||||
// applications should validate their own addresses - gh-7514
|
||||
return ValidationResult.VALID; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions, |
||||
subjects, statements, null, null) { |
||||
@Nonnull |
||||
@Override |
||||
protected ValidationResult validateSignature(Assertion token, ValidationContext context) { |
||||
return ValidationResult.VALID; |
||||
} |
||||
}; |
||||
|
||||
static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) { |
||||
return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), engine, |
||||
validator) { |
||||
@Nonnull |
||||
@Override |
||||
protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { |
||||
return ValidationResult.VALID; |
||||
} |
||||
|
||||
@Nonnull |
||||
@Override |
||||
protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { |
||||
return ValidationResult.VALID; |
||||
} |
||||
|
||||
@Nonnull |
||||
@Override |
||||
protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { |
||||
return ValidationResult.VALID; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A tuple containing an OpenSAML {@link Response} and its associated authentication |
||||
* token. |
||||
* |
||||
* @since 5.4 |
||||
*/ |
||||
public static class ResponseToken { |
||||
|
||||
private final Saml2AuthenticationToken token; |
||||
|
||||
private final Response response; |
||||
|
||||
ResponseToken(Response response, Saml2AuthenticationToken token) { |
||||
this.token = token; |
||||
this.response = response; |
||||
} |
||||
|
||||
public Response getResponse() { |
||||
return this.response; |
||||
} |
||||
|
||||
public Saml2AuthenticationToken getToken() { |
||||
return this.token; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A tuple containing an OpenSAML {@link Assertion} and its associated authentication |
||||
* token. |
||||
* |
||||
* @since 5.4 |
||||
*/ |
||||
public static class AssertionToken { |
||||
|
||||
private final Saml2AuthenticationToken token; |
||||
|
||||
private final Assertion assertion; |
||||
|
||||
AssertionToken(Assertion assertion, Saml2AuthenticationToken token) { |
||||
this.token = token; |
||||
this.assertion = assertion; |
||||
} |
||||
|
||||
public Assertion getAssertion() { |
||||
return this.assertion; |
||||
} |
||||
|
||||
public Saml2AuthenticationToken getToken() { |
||||
return this.token; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,112 +0,0 @@
@@ -1,112 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication; |
||||
|
||||
import java.time.Clock; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.joda.time.DateTime; |
||||
import org.opensaml.saml.saml2.core.AuthnRequest; |
||||
import org.opensaml.saml.saml2.core.LogoutRequest; |
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the |
||||
* {@link HttpServletRequest} using OpenSAML. |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.7 |
||||
* @deprecated OpenSAML 3 has reached end-of-life so this version is no longer recommended |
||||
*/ |
||||
@Deprecated |
||||
public final class OpenSaml3AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver { |
||||
|
||||
private final OpenSamlAuthenticationRequestResolver authnRequestResolver; |
||||
|
||||
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> { |
||||
}; |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
/** |
||||
* Construct a {@link OpenSaml3AuthenticationRequestResolver} |
||||
*/ |
||||
public OpenSaml3AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver); |
||||
} |
||||
|
||||
@Override |
||||
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) { |
||||
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> { |
||||
authnRequest.setIssueInstant(new DateTime(this.clock.millis())); |
||||
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest)); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} |
||||
* @param contextConsumer a consumer that accepts an {@link AuthnRequestContext} |
||||
*/ |
||||
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) { |
||||
Assert.notNull(contextConsumer, "contextConsumer cannot be null"); |
||||
this.contextConsumer = contextConsumer; |
||||
} |
||||
|
||||
/** |
||||
* Use this {@link Clock} for generating the issued {@link DateTime} |
||||
* @param clock the {@link Clock} to use |
||||
*/ |
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock must not be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
public static final class AuthnRequestContext { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final RelyingPartyRegistration registration; |
||||
|
||||
private final AuthnRequest authnRequest; |
||||
|
||||
public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration, |
||||
AuthnRequest authnRequest) { |
||||
this.request = request; |
||||
this.registration = registration; |
||||
this.authnRequest = authnRequest; |
||||
} |
||||
|
||||
public HttpServletRequest getRequest() { |
||||
return this.request; |
||||
} |
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() { |
||||
return this.registration; |
||||
} |
||||
|
||||
public AuthnRequest getAuthnRequest() { |
||||
return this.authnRequest; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,124 +0,0 @@
@@ -1,124 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2021 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout; |
||||
|
||||
import java.time.Clock; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.joda.time.DateTime; |
||||
import org.opensaml.saml.saml2.core.LogoutRequest; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Requests with |
||||
* OpenSAML 3 |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.6 |
||||
* @deprecated Because OpenSAML 3 has reached End-of-Life, please update to |
||||
* {@code OpenSaml4LogoutRequestResolver} |
||||
*/ |
||||
public final class OpenSaml3LogoutRequestResolver implements Saml2LogoutRequestResolver { |
||||
|
||||
private final OpenSamlLogoutRequestResolver logoutRequestResolver; |
||||
|
||||
private Consumer<LogoutRequestParameters> parametersConsumer = (parameters) -> { |
||||
}; |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
/** |
||||
* Construct a {@link OpenSaml3LogoutRequestResolver} |
||||
*/ |
||||
public OpenSaml3LogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
this.logoutRequestResolver = new OpenSamlLogoutRequestResolver(relyingPartyRegistrationResolver); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) { |
||||
return this.logoutRequestResolver.resolve(request, authentication, (registration, logoutRequest) -> { |
||||
logoutRequest.setIssueInstant(new DateTime(this.clock.millis())); |
||||
this.parametersConsumer |
||||
.accept(new LogoutRequestParameters(request, registration, authentication, logoutRequest)); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} |
||||
* @param parametersConsumer a consumer that accepts an |
||||
* {@link LogoutRequestParameters} |
||||
*/ |
||||
public void setParametersConsumer(Consumer<LogoutRequestParameters> parametersConsumer) { |
||||
Assert.notNull(parametersConsumer, "parametersConsumer cannot be null"); |
||||
this.parametersConsumer = parametersConsumer; |
||||
} |
||||
|
||||
/** |
||||
* Use this {@link Clock} for generating the issued {@link DateTime} |
||||
* @param clock the {@link Clock} to use |
||||
*/ |
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock must not be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
public static final class LogoutRequestParameters { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final RelyingPartyRegistration registration; |
||||
|
||||
private final Authentication authentication; |
||||
|
||||
private final LogoutRequest logoutRequest; |
||||
|
||||
public LogoutRequestParameters(HttpServletRequest request, RelyingPartyRegistration registration, |
||||
Authentication authentication, LogoutRequest logoutRequest) { |
||||
this.request = request; |
||||
this.registration = registration; |
||||
this.authentication = authentication; |
||||
this.logoutRequest = logoutRequest; |
||||
} |
||||
|
||||
public HttpServletRequest getRequest() { |
||||
return this.request; |
||||
} |
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() { |
||||
return this.registration; |
||||
} |
||||
|
||||
public Authentication getAuthentication() { |
||||
return this.authentication; |
||||
} |
||||
|
||||
public LogoutRequest getLogoutRequest() { |
||||
return this.logoutRequest; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,120 +0,0 @@
@@ -1,120 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2021 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout; |
||||
|
||||
import java.time.Clock; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.joda.time.DateTime; |
||||
import org.opensaml.saml.saml2.core.LogoutResponse; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A {@link Saml2LogoutResponseResolver} for resolving SAML 2.0 Logout Responses with |
||||
* OpenSAML 3 |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.6 |
||||
* @deprecated Because OpenSAML 3 has reached End-of-Life, please update to |
||||
* {@code OpenSaml4LogoutResponseResolver} |
||||
*/ |
||||
public final class OpenSaml3LogoutResponseResolver implements Saml2LogoutResponseResolver { |
||||
|
||||
private final OpenSamlLogoutResponseResolver logoutResponseResolver; |
||||
|
||||
private Consumer<LogoutResponseParameters> parametersConsumer = (parameters) -> { |
||||
}; |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
/** |
||||
* Construct a {@link OpenSaml3LogoutResponseResolver} |
||||
*/ |
||||
public OpenSaml3LogoutResponseResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
this.logoutResponseResolver = new OpenSamlLogoutResponseResolver(relyingPartyRegistrationResolver); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication) { |
||||
return this.logoutResponseResolver.resolve(request, authentication, (registration, logoutResponse) -> { |
||||
logoutResponse.setIssueInstant(new DateTime(this.clock.millis())); |
||||
this.parametersConsumer |
||||
.accept(new LogoutResponseParameters(request, registration, authentication, logoutResponse)); |
||||
}); |
||||
} |
||||
|
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock must not be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
/** |
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutResponse} |
||||
* @param parametersConsumer a consumer that accepts an |
||||
* {@link LogoutResponseParameters} |
||||
*/ |
||||
public void setParametersConsumer(Consumer<LogoutResponseParameters> parametersConsumer) { |
||||
Assert.notNull(parametersConsumer, "parametersConsumer cannot be null"); |
||||
this.parametersConsumer = parametersConsumer; |
||||
} |
||||
|
||||
public static final class LogoutResponseParameters { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final RelyingPartyRegistration registration; |
||||
|
||||
private final Authentication authentication; |
||||
|
||||
private final LogoutResponse logoutResponse; |
||||
|
||||
public LogoutResponseParameters(HttpServletRequest request, RelyingPartyRegistration registration, |
||||
Authentication authentication, LogoutResponse logoutResponse) { |
||||
this.request = request; |
||||
this.registration = registration; |
||||
this.authentication = authentication; |
||||
this.logoutResponse = logoutResponse; |
||||
} |
||||
|
||||
public HttpServletRequest getRequest() { |
||||
return this.request; |
||||
} |
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() { |
||||
return this.registration; |
||||
} |
||||
|
||||
public Authentication getAuthentication() { |
||||
return this.authentication; |
||||
} |
||||
|
||||
public LogoutResponse getLogoutResponse() { |
||||
return this.logoutResponse; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,682 +0,0 @@
@@ -1,682 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.ObjectOutputStream; |
||||
import java.time.Instant; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import javax.xml.namespace.QName; |
||||
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport; |
||||
import org.joda.time.DateTime; |
||||
import org.joda.time.Duration; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.opensaml.core.xml.XMLObject; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; |
||||
import org.opensaml.core.xml.io.Marshaller; |
||||
import org.opensaml.core.xml.io.MarshallingException; |
||||
import org.opensaml.core.xml.schema.XSDateTime; |
||||
import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; |
||||
import org.opensaml.saml.common.assertion.ValidationContext; |
||||
import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; |
||||
import org.opensaml.saml.saml2.core.Assertion; |
||||
import org.opensaml.saml.saml2.core.Attribute; |
||||
import org.opensaml.saml.saml2.core.AttributeStatement; |
||||
import org.opensaml.saml.saml2.core.AttributeValue; |
||||
import org.opensaml.saml.saml2.core.Conditions; |
||||
import org.opensaml.saml.saml2.core.EncryptedAssertion; |
||||
import org.opensaml.saml.saml2.core.EncryptedAttribute; |
||||
import org.opensaml.saml.saml2.core.EncryptedID; |
||||
import org.opensaml.saml.saml2.core.NameID; |
||||
import org.opensaml.saml.saml2.core.OneTimeUse; |
||||
import org.opensaml.saml.saml2.core.Response; |
||||
import org.opensaml.saml.saml2.core.StatusCode; |
||||
import org.opensaml.saml.saml2.core.SubjectConfirmation; |
||||
import org.opensaml.saml.saml2.core.SubjectConfirmationData; |
||||
import org.opensaml.saml.saml2.core.impl.AttributeBuilder; |
||||
import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder; |
||||
import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder; |
||||
import org.opensaml.saml.saml2.core.impl.NameIDBuilder; |
||||
import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder; |
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants; |
||||
import org.w3c.dom.Element; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.core.Saml2Error; |
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes; |
||||
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; |
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials; |
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider.ResponseToken; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.atLeastOnce; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Tests for {@link OpenSamlAuthenticationProvider} |
||||
* |
||||
* @author Filip Hanik |
||||
* @author Josh Cummings |
||||
*/ |
||||
public class OpenSamlAuthenticationProviderTests { |
||||
|
||||
private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias"; |
||||
|
||||
private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; |
||||
|
||||
private static String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; |
||||
|
||||
private OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
|
||||
private Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("name", |
||||
Collections.emptyMap()); |
||||
|
||||
private Saml2Authentication authentication = new Saml2Authentication(this.principal, "response", |
||||
Collections.emptyList()); |
||||
|
||||
@Test |
||||
public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() { |
||||
assertThat(this.provider.supports(Saml2AuthenticationToken.class)) |
||||
.withFailMessage( |
||||
OpenSamlAuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() { |
||||
assertThat(!this.provider.supports(Authentication.class)) |
||||
.withFailMessage(OpenSamlAuthenticationProvider.class + "should not support " + Authentication.class) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { |
||||
Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory() |
||||
.getBuilder(Assertion.DEFAULT_ELEMENT_NAME).buildObject(Assertion.DEFAULT_ELEMENT_NAME); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate( |
||||
new Saml2AuthenticationToken(verifying(registration()).build(), serialize(assertion)))) |
||||
.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenXmlErrorThenThrowAuthenticationException() { |
||||
Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml"); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { |
||||
Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion()); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.INVALID_DESTINATION)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() { |
||||
Saml2AuthenticationToken token = token(); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
response.getAssertions().add(assertion()); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData() |
||||
.setNotOnOrAfter(DateTime.now().minus(Duration.standardDays(3))); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenMissingSubjectThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
assertion.setSubject(null); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenUsernameMissingThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
assertion.getSubject().getNameID().setValue(null); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
assertion.getSubject().getSubjectConfirmations() |
||||
.forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
this.provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenAssertionContainsAttributesThenItSucceeds() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
List<AttributeStatement> attributes = attributeStatements(); |
||||
assertion.getAttributeStatements().addAll(attributes); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
Authentication authentication = this.provider.authenticate(token); |
||||
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); |
||||
Map<String, Object> expected = new LinkedHashMap<>(); |
||||
expected.put("email", Arrays.asList("john.doe@example.com", "doe.john@example.com")); |
||||
expected.put("name", Collections.singletonList("John Doe")); |
||||
expected.put("age", Collections.singletonList(21)); |
||||
expected.put("website", Collections.singletonList("https://johndoe.com/")); |
||||
expected.put("registered", Collections.singletonList(true)); |
||||
expected.put("role", Arrays.asList("RoleTwo")); |
||||
Instant registeredDate = Instant.ofEpochMilli(DateTime.parse("1970-01-01T00:00:00Z").getMillis()); |
||||
expected.put("registeredDate", Collections.singletonList(registeredDate)); |
||||
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe"); |
||||
assertThat(principal.getAttributes()).isEqualTo(expected); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() { |
||||
Response response = response(); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() { |
||||
Response response = response(); |
||||
Assertion assertion = TestOpenSamlObjects.signed(assertion(), |
||||
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); |
||||
this.provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() { |
||||
Response response = response(); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); |
||||
this.provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
NameID nameId = assertion.getSubject().getNameID(); |
||||
EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId, |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
assertion.getSubject().setNameID(null); |
||||
assertion.getSubject().setEncryptedID(encryptedID); |
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); |
||||
this.provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedAttributeThenDecrypts() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); |
||||
statement.getEncryptedAttributes().add(attribute); |
||||
assertion.getAttributeStatements().add(statement); |
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); |
||||
Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); |
||||
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); |
||||
assertThat(principal.getAttribute("name")).containsExactly("value"); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() { |
||||
Response response = response(); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, registration() |
||||
.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()))); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); |
||||
} |
||||
|
||||
@Test |
||||
public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { |
||||
Response response = response(); |
||||
Assertion assertion = TestOpenSamlObjects.signed(assertion(), |
||||
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); |
||||
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, |
||||
TestSaml2X509Credentials.assertingPartyEncryptingCredential()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); |
||||
Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); |
||||
// the following code will throw an exception if authentication isn't serializable
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); |
||||
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); |
||||
objectOutputStream.writeObject(authentication); |
||||
objectOutputStream.flush(); |
||||
} |
||||
|
||||
@Test |
||||
public void createDefaultAssertionValidatorWhenAssertionThenValidates() { |
||||
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); |
||||
Assertion assertion = response.getAssertions().get(0); |
||||
OpenSamlAuthenticationProvider.AssertionToken assertionToken = new OpenSamlAuthenticationProvider.AssertionToken( |
||||
assertion, token()); |
||||
assertThat(OpenSamlAuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors()) |
||||
.isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() { |
||||
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
// @formatter:off
|
||||
provider.setAssertionValidator((assertionToken) -> OpenSamlAuthenticationProvider |
||||
.createDefaultAssertionValidator((token) -> new ValidationContext()) |
||||
.convert(assertionToken) |
||||
.concat(new Saml2Error("wrong error", "wrong error")) |
||||
); |
||||
// @formatter:on
|
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME); |
||||
assertion.getConditions().getConditions().add(oneTimeUse); |
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
ASSERTING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) |
||||
.satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_ASSERTION)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenCustomAssertionValidatorThenUses() { |
||||
Converter<OpenSamlAuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> validator = mock( |
||||
Converter.class); |
||||
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
// @formatter:off
|
||||
provider.setAssertionValidator((assertionToken) -> OpenSamlAuthenticationProvider.createDefaultAssertionValidator() |
||||
.convert(assertionToken) |
||||
.concat(validator.convert(assertionToken)) |
||||
); |
||||
// @formatter:on
|
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
ASSERTING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
given(validator.convert(any(OpenSamlAuthenticationProvider.AssertionToken.class))) |
||||
.willReturn(Saml2ResponseValidatorResult.success()); |
||||
provider.authenticate(token); |
||||
verify(validator).convert(any(OpenSamlAuthenticationProvider.AssertionToken.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() { |
||||
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success()); |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(), |
||||
RELYING_PARTY_ENTITY_ID); // broken
|
||||
// signature
|
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
ASSERTING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> provider.authenticate(token)) |
||||
.satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenValidationContextCustomizedThenUsers() { |
||||
Map<String, Object> parameters = new HashMap<>(); |
||||
parameters.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton("blah")); |
||||
ValidationContext context = mock(ValidationContext.class); |
||||
given(context.getStaticParameters()).willReturn(parameters); |
||||
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
provider.setAssertionValidator( |
||||
OpenSamlAuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context)); |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
response.getAssertions().add(assertion); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
ASSERTING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) |
||||
.satisfies((error) -> assertThat(error).hasMessageContaining("Invalid assertion")); |
||||
// @formatter:on
|
||||
verify(context, atLeastOnce()).getStaticParameters(); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { |
||||
Response response = response(); |
||||
Assertion assertion = TestOpenSamlObjects.signed(assertion(), |
||||
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, |
||||
SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
this.provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void setAssertionValidatorWhenNullThenIllegalArgument() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.provider.setAssertionValidator(null)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() { |
||||
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
ResponseToken responseToken = new ResponseToken(response, token); |
||||
Saml2Authentication authentication = OpenSamlAuthenticationProvider |
||||
.createDefaultResponseAuthenticationConverter().convert(responseToken); |
||||
assertThat(authentication.getName()).isEqualTo("test@saml.user"); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() { |
||||
Converter<ResponseToken, Saml2Authentication> authenticationConverter = mock(Converter.class); |
||||
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
||||
provider.setResponseAuthenticationConverter(authenticationConverter); |
||||
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
provider.authenticate(token); |
||||
verify(authenticationConverter).convert(any()); |
||||
} |
||||
|
||||
@Test |
||||
public void setResponseAuthenticationConverterWhenNullThenIllegalArgument() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void setResponseElementsDecrypterWhenNullThenIllegalArgument() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseElementsDecrypter(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void setAssertionElementsDecrypterWhenNullThenIllegalArgument() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setAssertionElementsDecrypter(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject()); |
||||
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
this.provider.setResponseElementsDecrypter((tuple) -> tuple.getResponse().getAssertions().add(assertion)); |
||||
Authentication authentication = this.provider.authenticate(token); |
||||
assertThat(authentication.getName()).isEqualTo("test@saml.user"); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() { |
||||
Response response = response(); |
||||
Assertion assertion = assertion(); |
||||
EncryptedID id = new EncryptedIDBuilder().buildObject(); |
||||
id.setEncryptedData(new EncryptedDataBuilder().buildObject()); |
||||
assertion.getSubject().setEncryptedID(id); |
||||
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), |
||||
RELYING_PARTY_ENTITY_ID); |
||||
response.getAssertions().add(assertion); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
this.provider.setAssertionElementsDecrypter((tuple) -> { |
||||
NameID name = new NameIDBuilder().buildObject(); |
||||
name.setValue("decrypted name"); |
||||
tuple.getAssertion().getSubject().setNameID(name); |
||||
}); |
||||
Authentication authentication = this.provider.authenticate(token); |
||||
assertThat(authentication.getName()).isEqualTo("decrypted name"); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenResponseStatusIsNotSuccessThenFails() { |
||||
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion( |
||||
(r) -> r.setStatus(TestOpenSamlObjects.status(StatusCode.AUTHN_FAILED))); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
assertThatExceptionOfType(Saml2AuthenticationException.class) |
||||
.isThrownBy(() -> this.provider.authenticate(token)) |
||||
.satisfies(errorOf(Saml2ErrorCodes.INVALID_RESPONSE, "Invalid status")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenResponseStatusIsSuccessThenSucceeds() { |
||||
Response response = TestOpenSamlObjects |
||||
.signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.successStatus())); |
||||
Saml2AuthenticationToken token = token(response, verifying(registration())); |
||||
Authentication authentication = this.provider.authenticate(token); |
||||
assertThat(authentication.getName()).isEqualTo("test@saml.user"); |
||||
} |
||||
|
||||
private <T extends XMLObject> T build(QName qName) { |
||||
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); |
||||
} |
||||
|
||||
private String serialize(XMLObject object) { |
||||
try { |
||||
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); |
||||
Element element = marshaller.marshall(object); |
||||
return SerializeSupport.nodeToString(element); |
||||
} |
||||
catch (MarshallingException ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
} |
||||
|
||||
private Consumer<Saml2AuthenticationException> errorOf(String errorCode) { |
||||
return errorOf(errorCode, null); |
||||
} |
||||
|
||||
private Consumer<Saml2AuthenticationException> errorOf(String errorCode, String description) { |
||||
return (ex) -> { |
||||
assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode); |
||||
if (StringUtils.hasText(description)) { |
||||
assertThat(ex.getSaml2Error().getDescription()).contains(description); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private Response response() { |
||||
Response response = TestOpenSamlObjects.response(); |
||||
response.setIssueInstant(DateTime.now()); |
||||
return response; |
||||
} |
||||
|
||||
private Response response(String destination, String issuerEntityId) { |
||||
Response response = TestOpenSamlObjects.response(destination, issuerEntityId); |
||||
response.setIssueInstant(DateTime.now()); |
||||
return response; |
||||
} |
||||
|
||||
private Assertion assertion() { |
||||
Assertion assertion = TestOpenSamlObjects.assertion(); |
||||
assertion.setIssueInstant(DateTime.now()); |
||||
for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { |
||||
SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); |
||||
data.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); |
||||
data.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); |
||||
} |
||||
Conditions conditions = assertion.getConditions(); |
||||
conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); |
||||
conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); |
||||
return assertion; |
||||
} |
||||
|
||||
private List<AttributeStatement> attributeStatements() { |
||||
List<AttributeStatement> attributeStatements = TestOpenSamlObjects.attributeStatements(); |
||||
AttributeBuilder attributeBuilder = new AttributeBuilder(); |
||||
Attribute registeredDateAttr = attributeBuilder.buildObject(); |
||||
registeredDateAttr.setName("registeredDate"); |
||||
XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, |
||||
XSDateTime.TYPE_NAME); |
||||
registeredDate.setValue(DateTime.parse("1970-01-01T00:00:00Z")); |
||||
registeredDateAttr.getAttributeValues().add(registeredDate); |
||||
attributeStatements.get(0).getAttributes().add(registeredDateAttr); |
||||
return attributeStatements; |
||||
} |
||||
|
||||
private Saml2AuthenticationToken token() { |
||||
Response response = response(); |
||||
RelyingPartyRegistration registration = verifying(registration()).build(); |
||||
return new Saml2AuthenticationToken(registration, serialize(response)); |
||||
} |
||||
|
||||
private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration) { |
||||
return new Saml2AuthenticationToken(registration.build(), serialize(response)); |
||||
} |
||||
|
||||
private RelyingPartyRegistration.Builder registration() { |
||||
return TestRelyingPartyRegistrations.noCredentials().entityId(RELYING_PARTY_ENTITY_ID) |
||||
.assertionConsumerServiceLocation(DESTINATION) |
||||
.assertingPartyDetails((party) -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); |
||||
} |
||||
|
||||
private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { |
||||
return builder.assertingPartyDetails((party) -> party |
||||
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); |
||||
} |
||||
|
||||
private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) { |
||||
return builder |
||||
.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())); |
||||
} |
||||
|
||||
} |
||||
@ -1,66 +0,0 @@
@@ -1,66 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2021 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link OpenSaml3LogoutRequestResolver} |
||||
*/ |
||||
public class OpenSaml3LogoutRequestResolverTests { |
||||
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = mock(RelyingPartyRegistrationResolver.class); |
||||
|
||||
@Test |
||||
public void resolveWhenCustomParametersConsumerThenUses() { |
||||
OpenSaml3LogoutRequestResolver logoutRequestResolver = new OpenSaml3LogoutRequestResolver( |
||||
this.relyingPartyRegistrationResolver); |
||||
logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid")); |
||||
HttpServletRequest request = new MockHttpServletRequest(); |
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration() |
||||
.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout")) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password"); |
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration); |
||||
Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication); |
||||
assertThat(logoutRequest.getId()).isEqualTo("myid"); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConsumerWhenNullThenIllegalArgument() { |
||||
OpenSaml3LogoutRequestResolver logoutRequestResolver = new OpenSaml3LogoutRequestResolver( |
||||
this.relyingPartyRegistrationResolver); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> logoutRequestResolver.setParametersConsumer(null)); |
||||
} |
||||
|
||||
} |
||||
@ -1,78 +0,0 @@
@@ -1,78 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2021 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout; |
||||
|
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.opensaml.saml.saml2.core.LogoutRequest; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.saml2.core.Saml2ParameterNames; |
||||
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; |
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutResponseResolver.LogoutResponseParameters; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Tests for {@link OpenSaml3LogoutResponseResolver} |
||||
*/ |
||||
public class OpenSaml3LogoutResponseResolverTests { |
||||
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = mock(RelyingPartyRegistrationResolver.class); |
||||
|
||||
@Test |
||||
public void resolveWhenCustomParametersConsumerThenUses() { |
||||
OpenSaml3LogoutResponseResolver logoutResponseResolver = new OpenSaml3LogoutResponseResolver( |
||||
this.relyingPartyRegistrationResolver); |
||||
Consumer<LogoutResponseParameters> parametersConsumer = mock(Consumer.class); |
||||
logoutResponseResolver.setParametersConsumer(parametersConsumer); |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration() |
||||
.assertingPartyDetails( |
||||
(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout")) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password"); |
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); |
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, |
||||
Saml2Utils.samlEncode(OpenSamlSigningUtils.serialize(logoutRequest).getBytes())); |
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration); |
||||
Saml2LogoutResponse logoutResponse = logoutResponseResolver.resolve(request, authentication); |
||||
assertThat(logoutResponse).isNotNull(); |
||||
verify(parametersConsumer).accept(any()); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConsumerWhenNullThenIllegalArgument() { |
||||
OpenSaml3LogoutRequestResolver logoutRequestResolver = new OpenSaml3LogoutRequestResolver( |
||||
this.relyingPartyRegistrationResolver); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> logoutRequestResolver.setParametersConsumer(null)); |
||||
} |
||||
|
||||
} |
||||
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java
0
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java → saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java
Loading…
Reference in new issue