diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
index 8ab64e76dd..02cf9aea6f 100644
--- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
+++ b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
@@ -9,6 +9,4 @@ dependencies {
compile("org.opensaml:opensaml-saml-impl")
provided 'javax.servlet:javax.servlet-api'
-
- testCompile powerMock2Dependencies
}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
index 2ca8c9380f..a67d36e98d 100644
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
+++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
@@ -16,12 +16,16 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.util.Assert;
@@ -75,59 +79,18 @@ import java.util.Set;
import static java.lang.String.format;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.DECRYPTION_ERROR;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_DESTINATION;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_ISSUER;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.MALFORMED_RESPONSE_DATA;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.SUBJECT_NOT_FOUND;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS;
-import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.USERNAME_NOT_FOUND;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.StringUtils.hasText;
/**
- * 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.
- *
- *
- * 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).
- *
- *
- * 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.
- *
- *
- * 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.
- *
- *
- * This provider supports two types of encrypted SAML elements
- *
- * If the assertion is encrypted, then signature validation on the assertion is no longer required.
- *
- *
- * This provider does not perform an X509 certificate validation on the configured asserting party, IDP, verification
- * certificates.
- *
* @since 5.2
- * @see SAML 2 StatusResponse
- * @see OpenSAML 3
*/
public final class OpenSamlAuthenticationProvider implements AuthenticationProvider {
private static Log logger = LogFactory.getLog(OpenSamlAuthenticationProvider.class);
private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
- private Converter> authoritiesExtractor =
- (a -> singletonList(new SimpleGrantedAuthority("ROLE_USER")));
+ private Converter> authoritiesExtractor = (a -> singletonList(new SimpleGrantedAuthority("ROLE_USER")));
private GrantedAuthoritiesMapper authoritiesMapper = (a -> a);
private Duration responseTimeValidationSkew = Duration.ofMinutes(5);
@@ -175,16 +138,20 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication;
String xml = token.getSaml2Response();
Response samlResponse = getSaml2Response(xml);
+
Assertion assertion = validateSaml2Response(token, token.getRecipientUri(), samlResponse);
- String username = getUsername(token, assertion);
+ final String username = getUsername(token, assertion);
+ if (username == null) {
+ throw new UsernameNotFoundException("Assertion [" +
+ assertion.getID() +
+ "] is missing a user identifier");
+ }
return new Saml2Authentication(
() -> username, token.getSaml2Response(),
this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion))
);
- } catch (Saml2AuthenticationException e) {
- throw e;
- } catch (Exception e) {
- throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
+ }catch (Saml2Exception | IllegalArgumentException e) {
+ throw new AuthenticationServiceException(e.getMessage(), e);
}
}
@@ -200,116 +167,93 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
return this.authoritiesExtractor.convert(assertion);
}
- private String getUsername(Saml2AuthenticationToken token, Assertion assertion) throws Saml2AuthenticationException {
- String username = null;
- Subject subject = assertion.getSubject();
+ private String getUsername(Saml2AuthenticationToken token, Assertion assertion) {
+ final Subject subject = assertion.getSubject();
if (subject == null) {
- throw authException(SUBJECT_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a subject");
+ return null;
}
if (subject.getNameID() != null) {
- username = subject.getNameID().getValue();
+ return subject.getNameID().getValue();
}
- else if (subject.getEncryptedID() != null) {
+ if (subject.getEncryptedID() != null) {
NameID nameId = decrypt(token, subject.getEncryptedID());
- username = nameId.getValue();
+ return nameId.getValue();
}
- if (username == null) {
- throw authException(USERNAME_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a user identifier");
- }
- return username;
+ return null;
}
private Assertion validateSaml2Response(Saml2AuthenticationToken token,
String recipient,
- Response samlResponse) throws Saml2AuthenticationException {
- //optional validation if the response contains a destination
+ Response samlResponse) throws AuthenticationException {
if (hasText(samlResponse.getDestination()) && !recipient.equals(samlResponse.getDestination())) {
- throw authException(INVALID_DESTINATION, "Invalid SAML response destination: " + samlResponse.getDestination());
+ throw new Saml2Exception("Invalid SAML response destination: " + samlResponse.getDestination());
}
- String issuer = samlResponse.getIssuer().getValue();
+ final String issuer = samlResponse.getIssuer().getValue();
if (logger.isDebugEnabled()) {
- logger.debug("Validating SAML response from " + issuer);
+ logger.debug("Processing SAML response from " + issuer);
}
- if (!hasText(issuer) || (!issuer.equals(token.getIdpEntityId()))) {
- String message = String.format("Response issuer '%s' doesn't match '%s'", issuer, token.getIdpEntityId());
- throw authException(INVALID_ISSUER, message);
+ if (token == null) {
+ throw new Saml2Exception(format("SAML 2 Provider for %s was not found.", issuer));
}
- Saml2AuthenticationException lastValidationError = null;
-
boolean responseSigned = hasValidSignature(samlResponse, token);
for (Assertion a : samlResponse.getAssertions()) {
if (logger.isDebugEnabled()) {
logger.debug("Checking plain assertion validity " + a);
}
- try {
- validateAssertion(recipient, a, token, !responseSigned);
+ if (isValidAssertion(recipient, a, token, !responseSigned)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found valid assertion. Skipping potential others.");
+ }
return a;
- } catch (Saml2AuthenticationException e) {
- lastValidationError = e;
}
}
for (EncryptedAssertion ea : samlResponse.getEncryptedAssertions()) {
if (logger.isDebugEnabled()) {
logger.debug("Checking encrypted assertion validity " + ea);
}
- try {
- Assertion a = decrypt(token, ea);
- validateAssertion(recipient, a, token, false);
+
+ Assertion a = decrypt(token, ea);
+ if (isValidAssertion(recipient, a, token, false)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found valid encrypted assertion. Skipping potential others.");
+ }
return a;
- } catch (Saml2AuthenticationException e) {
- lastValidationError = e;
}
}
- if (lastValidationError != null) {
- throw lastValidationError;
- }
- else {
- throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response.");
- }
+ throw new InsufficientAuthenticationException("Unable to find a valid assertion");
}
- private boolean hasValidSignature(SignableSAMLObject samlObject, Saml2AuthenticationToken token) {
- if (!samlObject.isSigned()) {
- if (logger.isDebugEnabled()) {
- logger.debug("SAML object is not signed, no signatures found");
- }
+ private boolean hasValidSignature(SignableSAMLObject samlResponse, Saml2AuthenticationToken token) {
+ if (!samlResponse.isSigned()) {
return false;
}
- List verificationKeys = getVerificationCertificates(token);
+ final List verificationKeys = getVerificationKeys(token);
if (verificationKeys.isEmpty()) {
return false;
}
- for (X509Certificate certificate : verificationKeys) {
- Credential credential = getVerificationCredential(certificate);
+ for (X509Certificate key : verificationKeys) {
+ final Credential credential = getVerificationCredential(key);
try {
- SignatureValidator.validate(samlObject.getSignature(), credential);
- if (logger.isDebugEnabled()) {
- logger.debug("Valid signature found in SAML object:"+samlObject.getClass().getName());
- }
+ SignatureValidator.validate(samlResponse.getSignature(), credential);
return true;
}
catch (SignatureException ignored) {
- if (logger.isTraceEnabled()) {
- logger.trace("Signature validation failed with cert:"+certificate.toString(), ignored);
- }
- else if (logger.isDebugEnabled()) {
- logger.debug("Signature validation failed with cert:"+certificate.toString());
- }
+ logger.debug("Signature validation failed", ignored);
}
}
return false;
}
- private void validateAssertion(String recipient, Assertion a, Saml2AuthenticationToken token, boolean signatureRequired) {
- SAML20AssertionValidator validator = getAssertionValidator(token);
+ private boolean isValidAssertion(String recipient, Assertion a, Saml2AuthenticationToken token, boolean signatureRequired) {
+ final SAML20AssertionValidator validator = getAssertionValidator(token);
Map validationParams = new HashMap<>();
validationParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false);
validationParams.put(
SAML2AssertionValidationParameters.CLOCK_SKEW,
- this.responseTimeValidationSkew.toMillis()
+ this.responseTimeValidationSkew
);
validationParams.put(
SAML2AssertionValidationParameters.COND_VALID_AUDIENCES,
@@ -323,78 +267,55 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
if (logger.isDebugEnabled()) {
logger.debug(format("Assertion [%s] does not a valid signature.", a.getID()));
}
- throw authException(Saml2ErrorCodes.INVALID_SIGNATURE, "Assertion doesn't have a valid signature.");
+ return false;
}
- //ensure that OpenSAML doesn't attempt signature validation, already performed
a.setSignature(null);
- //remainder of assertion validation
+ // validation for recipient
ValidationContext vctx = new ValidationContext(validationParams);
try {
- ValidationResult result = validator.validate(a, vctx);
- boolean valid = result.equals(ValidationResult.VALID);
+ final ValidationResult result = validator.validate(a, vctx);
+ final boolean valid = result.equals(ValidationResult.VALID);
if (!valid) {
if (logger.isDebugEnabled()) {
- logger.debug(format("Failed to validate assertion from %s", token.getIdpEntityId()));
+ logger.debug(format("Failed to validate assertion from %s with user %s", token.getIdpEntityId(),
+ getUsername(token, a)
+ ));
}
- throw authException(Saml2ErrorCodes.INVALID_ASSERTION, vctx.getValidationFailureMessage());
}
+ return valid;
}
catch (AssertionValidationException e) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to validate assertion:", e);
}
- throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
+ return false;
}
}
- private Response getSaml2Response(String xml) throws Saml2Exception, Saml2AuthenticationException {
- try {
- Object result = this.saml.resolve(xml);
- if (result instanceof Response) {
- return (Response) result;
- }
- else {
- throw authException(UNKNOWN_RESPONSE_CLASS, "Invalid response class:" + result.getClass().getName());
- }
- } catch (Saml2Exception x) {
- throw authException(MALFORMED_RESPONSE_DATA, x.getMessage(), x);
+ private Response getSaml2Response(String xml) throws Saml2Exception, AuthenticationException {
+ final Object result = this.saml.resolve(xml);
+ if (result == null) {
+ throw new AuthenticationCredentialsNotFoundException("SAMLResponse returned null object");
}
-
- }
-
- private Saml2Error validationError(String code, String description) {
- return new Saml2Error(
- code,
- description
- );
- }
-
- private Saml2AuthenticationException authException(String code, String description) throws Saml2AuthenticationException {
- return new Saml2AuthenticationException(
- validationError(code, description)
- );
- }
-
-
- private Saml2AuthenticationException authException(String code, String description, Exception cause) throws Saml2AuthenticationException {
- return new Saml2AuthenticationException(
- validationError(code, description),
- cause
- );
+ else if (result instanceof Response) {
+ return (Response) result;
+ }
+ throw new IllegalArgumentException("Invalid response class:"+result.getClass().getName());
}
private SAML20AssertionValidator getAssertionValidator(Saml2AuthenticationToken provider) {
List conditions = Collections.singletonList(new AudienceRestrictionConditionValidator());
- BearerSubjectConfirmationValidator subjectConfirmationValidator = new BearerSubjectConfirmationValidator();
+ final BearerSubjectConfirmationValidator subjectConfirmationValidator =
+ new BearerSubjectConfirmationValidator();
List subjects = Collections.singletonList(subjectConfirmationValidator);
List statements = Collections.emptyList();
Set credentials = new HashSet<>();
- for (X509Certificate key : getVerificationCertificates(provider)) {
- Credential cred = getVerificationCredential(key);
+ for (X509Certificate key : getVerificationKeys(provider)) {
+ final Credential cred = getVerificationCredential(key);
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
@@ -424,38 +345,37 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
return decrypter;
}
- private Assertion decrypt(Saml2AuthenticationToken token, EncryptedAssertion assertion)
- throws Saml2AuthenticationException {
- Saml2AuthenticationException last = null;
+ private Assertion decrypt(Saml2AuthenticationToken token, EncryptedAssertion assertion) {
+ Saml2Exception last = null;
List decryptionCredentials = getDecryptionCredentials(token);
if (decryptionCredentials.isEmpty()) {
- throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
+ throw new Saml2Exception("No valid decryption credentials found.");
}
for (Saml2X509Credential key : decryptionCredentials) {
- Decrypter decrypter = getDecrypter(key);
+ final Decrypter decrypter = getDecrypter(key);
try {
return decrypter.decrypt(assertion);
}
catch (DecryptionException e) {
- last = authException(DECRYPTION_ERROR, e.getMessage(), e);
+ last = new Saml2Exception(e);
}
}
throw last;
}
- private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion) throws Saml2AuthenticationException {
- Saml2AuthenticationException last = null;
+ private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion) {
+ Saml2Exception last = null;
List decryptionCredentials = getDecryptionCredentials(token);
if (decryptionCredentials.isEmpty()) {
- throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
+ throw new Saml2Exception("No valid decryption credentials found.");
}
for (Saml2X509Credential key : decryptionCredentials) {
- Decrypter decrypter = getDecrypter(key);
+ final Decrypter decrypter = getDecrypter(key);
try {
return (NameID) decrypter.decrypt(assertion);
}
catch (DecryptionException e) {
- last = authException(DECRYPTION_ERROR, e.getMessage(), e);
+ last = new Saml2Exception(e);
}
}
throw last;
@@ -471,7 +391,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
return result;
}
- private List getVerificationCertificates(Saml2AuthenticationToken token) {
+ private List getVerificationKeys(Saml2AuthenticationToken token) {
List result = new LinkedList<>();
for (Saml2X509Credential c : token.getX509Credentials()) {
if (c.isSignatureVerficationCredential()) {
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
deleted file mode 100644
index 86709cec1e..0000000000
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2002-2019 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 org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.util.Assert;
-
-/**
- * This exception is thrown for all SAML 2.0 related {@link Authentication} errors.
- *
- *
- * There are a number of scenarios where an error may occur, for example:
- *
- * - The response or assertion request is missing or malformed
- * - Missing or invalid subject
- * - Missing or invalid signatures
- * - The time period validation for the assertion fails
- * - One of the assertion conditions was not met
- * - Decryption failed
- * - Unable to locate a subject identifier, commonly known as username
- *
- *
- * @since 5.2
- */
-public class Saml2AuthenticationException extends AuthenticationException {
- private Saml2Error error;
-
- /**
- * Constructs a {@code Saml2AuthenticationException} using the provided parameters.
- *
- * @param error the {@link Saml2Error SAML 2.0 Error}
- */
- public Saml2AuthenticationException(Saml2Error error) {
- this(error, error.getDescription());
- }
-
- /**
- * Constructs a {@code Saml2AuthenticationException} using the provided parameters.
- *
- * @param error the {@link Saml2Error SAML 2.0 Error}
- * @param cause the root cause
- */
- public Saml2AuthenticationException(Saml2Error error, Throwable cause) {
- this(error, cause.getMessage(), cause);
- }
-
- /**
- * Constructs a {@code Saml2AuthenticationException} using the provided parameters.
- *
- * @param error the {@link Saml2Error SAML 2.0 Error}
- * @param message the detail message
- */
- public Saml2AuthenticationException(Saml2Error error, String message) {
- super(message);
- this.setError(error);
- }
-
- /**
- * Constructs a {@code Saml2AuthenticationException} using the provided parameters.
- *
- * @param error the {@link Saml2Error SAML 2.0 Error}
- * @param message the detail message
- * @param cause the root cause
- */
- public Saml2AuthenticationException(Saml2Error error, String message, Throwable cause) {
- super(message, cause);
- this.setError(error);
- }
-
- /**
- * Returns the {@link Saml2Error SAML 2.0 Error}.
- *
- * @return the {@link Saml2Error}
- */
- public Saml2Error getError() {
- return this.error;
- }
-
- private void setError(Saml2Error error) {
- Assert.notNull(error, "error cannot be null");
- this.error = error;
- }
-
- @Override
- public String toString() {
- final StringBuffer sb = new StringBuffer("Saml2AuthenticationException{");
- sb.append("error=").append(error);
- sb.append('}');
- return sb.toString();
- }
-}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
deleted file mode 100644
index 786ae0e011..0000000000
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2002-2019 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 org.springframework.security.core.SpringSecurityCoreVersion;
-import org.springframework.util.Assert;
-
-import java.io.Serializable;
-
-/**
- * A representation of an SAML 2.0 Error.
- *
- *
- * At a minimum, an error response will contain an error code.
- * The commonly used error code are defined in this class
- * or a new codes can be defined in the future as arbitrary strings.
- *
- * @since 5.2
- */
-public class Saml2Error implements Serializable {
- private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
-
- private final String errorCode;
- private final String description;
-
- /**
- * Constructs a {@code Saml2Error} using the provided parameters.
- *
- * @param errorCode the error code
- * @param description the error description
- */
- public Saml2Error(String errorCode, String description) {
- Assert.hasText(errorCode, "errorCode cannot be empty");
- this.errorCode = errorCode;
- this.description = description;
- }
-
- /**
- * Returns the error code.
- *
- * @return the error code
- */
- public final String getErrorCode() {
- return this.errorCode;
- }
-
- /**
- * Returns the error description.
- *
- * @return the error description
- */
- public final String getDescription() {
- return this.description;
- }
-
- @Override
- public String toString() {
- return "[" + this.getErrorCode() + "] " +
- (this.getDescription() != null ? this.getDescription() : "");
- }
-}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
deleted file mode 100644
index 2b98a5334c..0000000000
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2002-2019 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;
-
-/**
- * A list of SAML known 2 error codes used during SAML authentication.
- *
- * @since 5.2
- */
-public interface Saml2ErrorCodes {
- /**
- * SAML Data does not represent a SAML 2 Response object.
- * A valid XML object was received, but that object was not a
- * SAML 2 Response object of type {@code ResponseType} per specification
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=46
- */
- String UNKNOWN_RESPONSE_CLASS = "unknown_response_class";
- /**
- * The response data is malformed or incomplete.
- * An invalid XML object was received, and XML unmarshalling failed.
- */
- String MALFORMED_RESPONSE_DATA = "malformed_response_data";
- /**
- * Response destination does not match the request URL.
- * A SAML 2 response object was received at a URL that
- * did not match the URL stored in the {code Destination} attribute
- * in the Response object.
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38
- */
- String INVALID_DESTINATION = "invalid_destination";
- /**
- * The assertion was not valid.
- * The assertion used for authentication failed validation.
- * Details around the failure will be present in the error description.
- */
- String INVALID_ASSERTION = "invalid_assertion";
- /**
- * The signature of response or assertion was invalid.
- * Either the response or the assertion was missing a signature
- * or the signature could not be verified using the system's
- * configured credentials. Most commonly the IDP's
- * X509 certificate.
- */
- String INVALID_SIGNATURE = "invalid_signature";
- /**
- * The assertion did not contain a subject element.
- * The subject element, type SubjectType, contains
- * a {@code NameID} or an {@code EncryptedID} that is used
- * to assign the authenticated principal an identifier,
- * typically a username.
- *
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=18
- */
- String SUBJECT_NOT_FOUND = "subject_not_found";
- /**
- * The subject did not contain a user identifier
- * The assertion contained a subject element, but the subject
- * element did not have a {@code NameID} or {@code EncryptedID}
- * element
- *
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=18
- */
- String USERNAME_NOT_FOUND = "username_not_found";
- /**
- * The system failed to decrypt an assertion or a name identifier.
- * This error code will be thrown if the decryption of either a
- * {@code EncryptedAssertion} or {@code EncryptedID} fails.
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=17
- */
- String DECRYPTION_ERROR = "decryption_error";
- /**
- * An Issuer element contained a value that didn't
- * https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=15
- */
- String INVALID_ISSUER = "invalid_issuer";
- /**
- * An error happened during validation.
- * Used when internal, non classified, errors are caught during the
- * authentication process.
- */
- String INTERNAL_VALIDATION_ERROR = "internal_validation_error";
-}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
deleted file mode 100644
index 292619040d..0000000000
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2002-2019 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.credentials;
-
-import org.springframework.security.converter.RsaKeyConverters;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import java.io.ByteArrayInputStream;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION;
-import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION;
-import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING;
-import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION;
-
-public class Saml2X509CredentialTests {
-
- @Rule
- public ExpectedException exception = ExpectedException.none();
-
- private Saml2X509Credential credential;
- private PrivateKey key;
- private X509Certificate certificate;
-
- @Before
- public void setup() throws Exception {
- String keyData = "-----BEGIN PRIVATE KEY-----\n" +
- "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" +
- "VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" +
- "cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" +
- "Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" +
- "x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" +
- "wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" +
- "vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" +
- "8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" +
- "oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" +
- "EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" +
- "KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" +
- "YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" +
- "9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" +
- "INrtuLp4YHbgk1mi\n" +
- "-----END PRIVATE KEY-----";
- key = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(keyData.getBytes(UTF_8)));
- final CertificateFactory factory = CertificateFactory.getInstance("X.509");
- String certificateData = "-----BEGIN CERTIFICATE-----\n" +
- "MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" +
- "VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" +
- "A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" +
- "DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" +
- "MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" +
- "MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" +
- "TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" +
- "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" +
- "vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" +
- "+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" +
- "y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" +
- "XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" +
- "qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" +
- "RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" +
- "-----END CERTIFICATE-----";
- certificate = (X509Certificate) factory
- .generateCertificate(new ByteArrayInputStream(certificateData.getBytes(UTF_8)));
- }
-
- @Test
- public void constructorWhenRelyingPartyWithCredentialsThenItSucceeds() {
- new Saml2X509Credential(key, certificate, SIGNING);
- new Saml2X509Credential(key, certificate, SIGNING, DECRYPTION);
- new Saml2X509Credential(key, certificate, DECRYPTION);
- }
-
- @Test
- public void constructorWhenAssertingPartyWithCredentialsThenItSucceeds() {
- new Saml2X509Credential(certificate, VERIFICATION);
- new Saml2X509Credential(certificate, VERIFICATION, ENCRYPTION);
- new Saml2X509Credential(certificate, ENCRYPTION);
- }
-
- @Test
- public void constructorWhenRelyingPartyWithoutCredentialsThenItFails() {
- exception.expect(IllegalArgumentException.class);
- new Saml2X509Credential(null, (X509Certificate) null, SIGNING);
- }
-
- @Test
- public void constructorWhenRelyingPartyWithoutPrivateKeyThenItFails() {
- exception.expect(IllegalArgumentException.class);
- new Saml2X509Credential(null, certificate, SIGNING);
- }
-
- @Test
- public void constructorWhenRelyingPartyWithoutCertificateThenItFails() {
- exception.expect(IllegalArgumentException.class);
- new Saml2X509Credential(key, null, SIGNING);
- }
-
- @Test
- public void constructorWhenAssertingPartyWithoutCertificateThenItFails() {
- exception.expect(IllegalArgumentException.class);
- new Saml2X509Credential(null, SIGNING);
- }
-
- @Test
- public void constructorWhenRelyingPartyWithEncryptionUsageThenItFails() {
- exception.expect(IllegalStateException.class);
- new Saml2X509Credential(key, certificate, ENCRYPTION);
- }
-
- @Test
- public void constructorWhenRelyingPartyWithVerificationUsageThenItFails() {
- exception.expect(IllegalStateException.class);
- new Saml2X509Credential(key, certificate, VERIFICATION);
- }
-
- @Test
- public void constructorWhenAssertingPartyWithSigningUsageThenItFails() {
- exception.expect(IllegalStateException.class);
- new Saml2X509Credential(certificate, SIGNING);
- }
-
- @Test
- public void constructorWhenAssertingPartyWithDecryptionUsageThenItFails() {
- exception.expect(IllegalStateException.class);
- new Saml2X509Credential(certificate, DECRYPTION);
- }
-
-
-}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
deleted file mode 100644
index ac0cfc5e5e..0000000000
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * Copyright 2002-2019 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 org.springframework.security.core.Authentication;
-import org.springframework.security.saml2.Saml2Exception;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-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.SAML20AssertionValidator;
-import org.opensaml.saml.saml2.core.Assertion;
-import org.opensaml.saml.saml2.core.EncryptedAssertion;
-import org.opensaml.saml.saml2.core.EncryptedID;
-import org.opensaml.saml.saml2.core.Issuer;
-import org.opensaml.saml.saml2.core.Response;
-import org.opensaml.saml.saml2.core.Subject;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import java.util.Collections;
-
-import static java.util.Collections.emptyList;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.powermock.api.mockito.PowerMockito.doReturn;
-import static org.powermock.api.mockito.PowerMockito.mock;
-import static org.powermock.api.mockito.PowerMockito.when;
-import static org.springframework.test.util.AssertionErrors.assertTrue;
-import static org.springframework.util.StringUtils.hasText;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({OpenSamlImplementation.class, OpenSamlAuthenticationProvider.class})
-public class OpenSamlAuthenticationProviderTests {
-
- private OpenSamlAuthenticationProvider provider;
- private OpenSamlImplementation saml;
-
- @Rule
- ExpectedException exception = ExpectedException.none();
- private Saml2AuthenticationToken token;
-
- @Before
- public void setup() {
- saml = PowerMockito.mock(OpenSamlImplementation.class);
- PowerMockito.mockStatic(OpenSamlImplementation.class);
- when(OpenSamlImplementation.getInstance()).thenReturn(saml);
-
- provider = new OpenSamlAuthenticationProvider();
- token = new Saml2AuthenticationToken(
- "responseXml",
- "recipientUri",
- "idpEntityId",
- "localSpEntityId",
- emptyList()
- );
- }
-
- @Test
- public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() {
-
- assertTrue(
- OpenSamlAuthenticationProvider.class + "should support " + token.getClass(),
- provider.supports(token.getClass())
- );
- }
-
- @Test
- public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() {
- assertTrue(
- OpenSamlAuthenticationProvider.class + "should not support " + Authentication.class,
- !provider.supports(Authentication.class)
- );
- }
-
- @Test
- public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() {
- when(saml.resolve(any(String.class))).thenReturn(mock(Assertion.class));
- exception.expect(authenticationMatcher(Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS));
- provider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenXmlErrorThenThrowAuthenticationException() {
- when(saml.resolve(any(String.class))).thenThrow(new Saml2Exception("test"));
- exception.expect(authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA));
- provider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
- final Response response = mock(Response.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn("invalidRecipient");
- exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_DESTINATION));
- provider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(emptyList());
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.MALFORMED_RESPONSE_DATA,
- "No assertions found in response."
- )
- );
- provider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenInvalidSignatureThenThrowAuthenticationException() {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
-
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.INVALID_SIGNATURE
- )
- );
- provider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- final SAML20AssertionValidator validator = mock(SAML20AssertionValidator.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
-
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(true).when(
- spyProvider,
- "hasValidSignature",
- any(Assertion.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(validator).when(spyProvider, "getAssertionValidator", any(Saml2AuthenticationToken.class));
- when(validator.validate(
- any(Assertion.class),
- any(ValidationContext.class)
- )).thenReturn(ValidationResult.INVALID);
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.INVALID_ASSERTION
- )
- );
- spyProvider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenInternalErrorThenCatchAndThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- final SAML20AssertionValidator validator = mock(SAML20AssertionValidator.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
-
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(true).when(
- spyProvider,
- "hasValidSignature",
- any(Assertion.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(validator).when(spyProvider, "getAssertionValidator", any(Saml2AuthenticationToken.class));
- when(validator.validate(
- any(Assertion.class),
- any(ValidationContext.class)
- )).thenThrow(new AssertionValidationException());
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR
- )
- );
- spyProvider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenMissingSubjectThenThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
-
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(true).when(
- spyProvider,
- "hasValidSignature",
- any(Assertion.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
- PowerMockito.doNothing()
- .when(
- spyProvider,
- "validateAssertion",
- anyString(),
- any(Assertion.class),
- any(Saml2AuthenticationToken.class),
- anyBoolean()
- );
-
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.SUBJECT_NOT_FOUND
- )
- );
- spyProvider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenUsernameMissingThenThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- final Subject subject = mock(Subject.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
- when(assertion.getSubject()).thenReturn(subject);
-
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(true).when(
- spyProvider,
- "hasValidSignature",
- any(Assertion.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
- PowerMockito.doNothing()
- .when(
- spyProvider,
- "validateAssertion",
- anyString(),
- any(Assertion.class),
- any(Saml2AuthenticationToken.class),
- anyBoolean()
- );
-
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.USERNAME_NOT_FOUND
- )
- );
- spyProvider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final Assertion assertion = mock(Assertion.class);
- final Subject subject = mock(Subject.class);
- final EncryptedID nameID = mock(EncryptedID.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getAssertions()).thenReturn(Collections.singletonList(assertion));
- when(response.getEncryptedAssertions()).thenReturn(emptyList());
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
- when(assertion.getSubject()).thenReturn(subject);
- when(subject.getEncryptedID()).thenReturn(nameID);
-
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(true).when(
- spyProvider,
- "hasValidSignature",
- any(Assertion.class),
- any(Saml2AuthenticationToken.class)
- );
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
- PowerMockito.doNothing()
- .when(
- spyProvider,
- "validateAssertion",
- anyString(),
- any(Assertion.class),
- any(Saml2AuthenticationToken.class),
- anyBoolean()
- );
-
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.DECRYPTION_ERROR,
- "No valid decryption credentials found."
- )
- );
- spyProvider.authenticate(token);
- }
-
- @Test
- public void authenticateWhenDecryptionKeyIsMissingThenThrowAuthenticationException() throws Exception {
- final Response response = mock(Response.class);
- final Issuer issuer = mock(Issuer.class);
- final EncryptedAssertion assertion = mock(EncryptedAssertion.class);
- when(saml.resolve(any(String.class))).thenReturn(response);
- when(response.getDestination()).thenReturn(token.getRecipientUri());
- when(response.isSigned()).thenReturn(false);
- when(response.getIssuer()).thenReturn(issuer);
- when(issuer.getValue()).thenReturn(token.getIdpEntityId());
- when(response.getEncryptedAssertions()).thenReturn(Collections.singletonList(assertion));
-
- OpenSamlAuthenticationProvider spyProvider = PowerMockito.spy(this.provider);
- doReturn(false).when(
- spyProvider,
- "hasValidSignature",
- any(Response.class),
- any(Saml2AuthenticationToken.class)
- );
-
- exception.expect(
- authenticationMatcher(
- Saml2ErrorCodes.DECRYPTION_ERROR,
- "No valid decryption credentials found."
- )
- );
- spyProvider.authenticate(token);
- }
-
-
- private BaseMatcher authenticationMatcher(String code) {
- return authenticationMatcher(code, null);
- }
-
- private BaseMatcher authenticationMatcher(String code, String description) {
- return new BaseMatcher() {
- private Object value = null;
-
- @Override
- public boolean matches(Object item) {
- if (!(item instanceof Saml2AuthenticationException)) {
- value = item;
- return false;
- }
- Saml2AuthenticationException ex = (Saml2AuthenticationException) item;
- if (!code.equals(ex.getError().getErrorCode())) {
- value = item;
- return false;
- }
- if (hasText(description)) {
- if (!description.equals(ex.getError().getDescription())) {
- value = item;
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Expecting a " + Saml2AuthenticationException.class.getName() +
- " with code:" + code + " and description:" + description
- )
- .appendValue(value);
- }
- };
- }
-
-}
diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
index cf0c5dfd93..f405126680 100644
--- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
+++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
@@ -22,15 +22,11 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.MediaType;
-import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.util.AssertionErrors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
-import org.springframework.test.web.servlet.ResultMatcher;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
-import org.hamcrest.Matcher;
import org.joda.time.DateTime;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,11 +62,9 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.UUID;
-import javax.servlet.http.HttpSession;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildConditions;
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildIssuer;
@@ -80,9 +74,6 @@ import static org.springframework.security.samples.OpenSamlActionTestingSupport.
import static org.springframework.security.samples.OpenSamlActionTestingSupport.encryptNameId;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
-import static org.springframework.security.web.WebAttributes.AUTHENTICATION_EXCEPTION;
-import static org.springframework.test.util.AssertionErrors.assertEquals;
-import static org.springframework.test.util.AssertionErrors.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -95,7 +86,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class Saml2LoginIntegrationTests {
static final String LOCAL_SP_ENTITY_ID = "http://localhost:8080/saml2/service-provider-metadata/simplesamlphp";
- static final String USERNAME = "testuser@spring.security.saml";
@Autowired
MockMvc mockMvc;
@@ -107,21 +97,21 @@ public class Saml2LoginIntegrationTests {
}
@Test
- public void applicationAccessWhenSingleProviderAndUnauthenticatedThenRedirectsToAuthNRequest() throws Exception {
+ public void redirectToLoginPageSingleProvider() throws Exception {
mockMvc.perform(get("http://localhost:8080/some/url"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost:8080/saml2/authenticate/simplesamlphp"));
}
@Test
- public void authenticateRequestWhenUnauthenticatedThenRespondsWithRedirectAuthNRequestXML() throws Exception {
+ public void testAuthNRequest() throws Exception {
mockMvc.perform(get("http://localhost:8080/saml2/authenticate/simplesamlphp"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")));
}
@Test
- public void authenticateRequestWhenRelayStateThenRespondsWithRedirectAndEncodedRelayState() throws Exception {
+ public void testRelayState() throws Exception {
mockMvc.perform(
get("http://localhost:8080/saml2/authenticate/simplesamlphp")
.param("RelayState", "relay state value with spaces")
@@ -132,136 +122,96 @@ public class Saml2LoginIntegrationTests {
}
@Test
- public void authenticateWhenResponseIsSignedThenItSucceeds() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void signedResponse() throws Exception {
+ final String username = "testuser@spring.security.saml";
+ Assertion assertion = buildAssertion(username);
Response response = buildResponse(assertion);
signXmlObject(response, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/")
- .andExpect(authenticated().withUsername(USERNAME));
+ String xml = toXml(response);
+ mockMvc.perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername(username));
}
@Test
- public void authenticateWhenAssertionIsThenItSignedSucceeds() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void signedAssertion() throws Exception {
+ final String username = "testuser@spring.security.saml";
+ Assertion assertion = buildAssertion(username);
Response response = buildResponse(assertion);
signXmlObject(assertion, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/")
- .andExpect(authenticated().withUsername(USERNAME));
+ String xml = toXml(response);
+ final ResultActions actions = mockMvc
+ .perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername(username));
}
@Test
- public void authenticateWhenXmlObjectIsNotSignedThenItFails() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void unsigned() throws Exception {
+ Assertion assertion = buildAssertion("testuser@spring.security.saml");
Response response = buildResponse(assertion);
- sendResponse(response, "/login?error")
+ String xml = toXml(response);
+ mockMvc.perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/login?error"))
.andExpect(unauthenticated());
}
@Test
- public void authenticateWhenResponseIsSignedAndAssertionIsEncryptedThenItSucceeds() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void signedResponseEncryptedAssertion() throws Exception {
+ final String username = "testuser@spring.security.saml";
+ Assertion assertion = buildAssertion(username);
EncryptedAssertion encryptedAssertion =
OpenSamlActionTestingSupport.encryptAssertion(assertion, decodeCertificate(spCertificate));
Response response = buildResponse(encryptedAssertion);
signXmlObject(assertion, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/")
- .andExpect(authenticated().withUsername(USERNAME));
+ String xml = toXml(response);
+ final ResultActions actions = mockMvc
+ .perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername(username));
}
@Test
- public void authenticateWhenResponseIsNotSignedAndAssertionIsEncryptedThenItSucceeds() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void unsignedResponseEncryptedAssertion() throws Exception {
+ final String username = "testuser@spring.security.saml";
+ Assertion assertion = buildAssertion(username);
EncryptedAssertion encryptedAssertion =
OpenSamlActionTestingSupport.encryptAssertion(assertion, decodeCertificate(spCertificate));
Response response = buildResponse(encryptedAssertion);
- sendResponse(response, "/")
- .andExpect(authenticated().withUsername(USERNAME));
+ String xml = toXml(response);
+ final ResultActions actions = mockMvc
+ .perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername(username));
}
@Test
- public void authenticateWhenResponseIsSignedAndNameIDisEncryptedThenItSucceeds() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
+ public void signedResponseEncryptedNameId() throws Exception {
+ final String username = "testuser@spring.security.saml";
+ Assertion assertion = buildAssertion(username);
final EncryptedID nameId = encryptNameId(assertion.getSubject().getNameID(), decodeCertificate(spCertificate));
assertion.getSubject().setEncryptedID(nameId);
assertion.getSubject().setNameID(null);
Response response = buildResponse(assertion);
signXmlObject(assertion, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/")
- .andExpect(authenticated().withUsername(USERNAME));
- }
-
- @Test
- public void authenticateWhenSignatureKeysDontMatchThenItFails() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
- Response response = buildResponse(assertion);
- signXmlObject(assertion, getSigningCredential(spCertificate, spPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/login?error")
- .andExpect(
- saml2AuthenticationExceptionMatcher(
- "invalid_signature",
- equalTo("Assertion doesn't have a valid signature.")
- )
- );
- }
-
- @Test
- public void authenticateWhenNotOnOrAfterDontMatchThenItFails() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
- assertion.getConditions().setNotOnOrAfter(DateTime.now().minusDays(1));
- Response response = buildResponse(assertion);
- signXmlObject(assertion, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/login?error")
- .andExpect(
- saml2AuthenticationExceptionMatcher(
- "invalid_assertion",
- containsString("Assertion 'assertion' with NotOnOrAfter condition of")
- )
- );
- }
-
- @Test
- public void authenticateWhenNotOnOrBeforeDontMatchThenItFails() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
- assertion.getConditions().setNotBefore(DateTime.now().plusDays(1));
- Response response = buildResponse(assertion);
- signXmlObject(assertion, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/login?error")
- .andExpect(
- saml2AuthenticationExceptionMatcher(
- "invalid_assertion",
- containsString("Assertion 'assertion' with NotBefore condition of")
- )
- );
- }
-
- @Test
- public void authenticateWhenIssuerIsInvalidThenItFails() throws Exception {
- Assertion assertion = buildAssertion(USERNAME);
- Response response = buildResponse(assertion);
- response.getIssuer().setValue("invalid issuer");
- signXmlObject(response, getSigningCredential(idpCertificate, idpPrivateKey, UsageType.SIGNING));
- sendResponse(response, "/login?error")
- .andExpect(unauthenticated())
- .andExpect(
- saml2AuthenticationExceptionMatcher(
- "invalid_issuer",
- containsString(
- "Response issuer 'invalid issuer' doesn't match "+
- "'https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php'"
- )
- )
- );
- }
-
- private ResultActions sendResponse(
- Response response,
- String redirectUrl) throws Exception {
String xml = toXml(response);
- return mockMvc.perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
- .contentType(MediaType.APPLICATION_FORM_URLENCODED)
- .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
- .andExpect(status().is3xxRedirection())
- .andExpect(redirectedUrl(redirectUrl));
+ final ResultActions actions = mockMvc
+ .perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
+ .andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername(username));
}
private Response buildResponse(Assertion assertion) {
@@ -409,42 +359,4 @@ public class Saml2LoginIntegrationTests {
"RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" +
"-----END CERTIFICATE-----";
- private String spPrivateKey = "-----BEGIN PRIVATE KEY-----\n" +
- "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" +
- "VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" +
- "cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" +
- "Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" +
- "x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" +
- "wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" +
- "vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" +
- "8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" +
- "oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" +
- "EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" +
- "KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" +
- "YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" +
- "9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" +
- "INrtuLp4YHbgk1mi\n" +
- "-----END PRIVATE KEY-----";
-
- private static ResultMatcher saml2AuthenticationExceptionMatcher(
- String code,
- Matcher message
- ) {
- return result -> {
- final HttpSession session = result.getRequest().getSession(false);
- AssertionErrors.assertNotNull("HttpSession", session);
- Object exception = session.getAttribute(AUTHENTICATION_EXCEPTION);
- AssertionErrors.assertNotNull(AUTHENTICATION_EXCEPTION, exception);
- if (!(exception instanceof Saml2AuthenticationException)) {
- AssertionErrors.fail(
- "Invalid exception type",
- Saml2AuthenticationException.class,
- exception.getClass().getName()
- );
- }
- Saml2AuthenticationException se = (Saml2AuthenticationException) exception;
- assertEquals("SAML 2 Error Code", code, se.getError().getErrorCode());
- assertTrue("SAML 2 Error Description", message.matches(se.getError().getDescription()));
- };
- }
}