1
0
mirror of synced 2026-05-22 13:23:17 +00:00

Merge Formatting Changes

Issue gh-8945
This commit is contained in:
Rob Winch
2020-08-24 16:38:12 -05:00
2779 changed files with 82438 additions and 91751 deletions
@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.xml.XMLConstants;
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
@@ -28,29 +29,30 @@ import org.apache.commons.logging.LogFactory;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.springframework.security.saml2.Saml2Exception;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.setParserPool;
/**
* An initialization service for initializing OpenSAML. Each Spring Security OpenSAML-based component invokes
* the {@link #initialize()} method at static initialization time.
* An initialization service for initializing OpenSAML. Each Spring Security
* OpenSAML-based component invokes the {@link #initialize()} method at static
* initialization time.
*
* {@link #initialize()} is idempotent and may be safely called in custom classes that need OpenSAML to be
* initialized in order to function correctly. It's recommended that you call this {@link #initialize()} method
* when using Spring Security and OpenSAML instead of OpenSAML's {@link InitializationService#initialize()}.
* {@link #initialize()} is idempotent and may be safely called in custom classes that
* need OpenSAML to be initialized in order to function correctly. It's recommended that
* you call this {@link #initialize()} method when using Spring Security and OpenSAML
* instead of OpenSAML's {@link InitializationService#initialize()}.
*
* The primary purpose of {@link #initialize()} is to prepare OpenSAML's {@link XMLObjectProviderRegistry}
* with some reasonable defaults. Any changes that Spring Security makes to the registry happen in this method.
* The primary purpose of {@link #initialize()} is to prepare OpenSAML's
* {@link XMLObjectProviderRegistry} with some reasonable defaults. Any changes that
* Spring Security makes to the registry happen in this method.
*
* To override those defaults, call {@link #requireInitialize(Consumer)} and change the registry:
* To override those defaults, call {@link #requireInitialize(Consumer)} and change the
* registry:
*
* <pre>
* static {
* OpenSamlInitializationService.requireInitialize(registry -> {
* OpenSamlInitializationService.requireInitialize((registry) -> {
* registry.setParserPool(...);
* registry.getBuilderFactory().registerBuilder(...);
* });
@@ -59,45 +61,53 @@ import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.setP
*
* {@link #requireInitialize(Consumer)} may only be called once per application.
*
* If the application already initialized OpenSAML before {@link #requireInitialize(Consumer)} was called,
* then the configuration changes will not be applied and an exception will be thrown. The reason for this is to
* alert you to the fact that there are likely some initialization ordering problems in your application that
* would otherwise lead to an unpredictable state.
* If the application already initialized OpenSAML before
* {@link #requireInitialize(Consumer)} was called, then the configuration changes will
* not be applied and an exception will be thrown. The reason for this is to alert you to
* the fact that there are likely some initialization ordering problems in your
* application that would otherwise lead to an unpredictable state.
*
* If you must change the registry's configuration in multiple places in your application, you are expected
* to handle the initialization ordering issues yourself instead of trying to call {@link #requireInitialize(Consumer)}
* multiple times.
* If you must change the registry's configuration in multiple places in your application,
* you are expected to handle the initialization ordering issues yourself instead of
* trying to call {@link #requireInitialize(Consumer)} multiple times.
*
* @author Josh Cummings
* @since 5.4
*/
public class OpenSamlInitializationService {
public final class OpenSamlInitializationService {
private static final Log log = LogFactory.getLog(OpenSamlInitializationService.class);
private static final AtomicBoolean initialized = new AtomicBoolean(false);
private OpenSamlInitializationService() {
}
/**
* Ready OpenSAML for use and configure it with reasonable defaults.
*
* Initialization is guaranteed to happen only once per application. This method will passively return
* {@code false} if initialization already took place earlier in the application.
*
* @return whether or not initialization was performed. The first thread to initialize OpenSAML will
* return {@code true} while the rest will return {@code false}.
* Initialization is guaranteed to happen only once per application. This method will
* passively return {@code false} if initialization already took place earlier in the
* application.
* @return whether or not initialization was performed. The first thread to initialize
* OpenSAML will return {@code true} while the rest will return {@code false}.
* @throws Saml2Exception if OpenSAML failed to initialize
*/
public static boolean initialize() {
return initialize(registry -> {});
return initialize((registry) -> {
});
}
/**
* Ready OpenSAML for use, configure it with reasonable defaults, and modify the {@link XMLObjectProviderRegistry}
* using the provided {@link Consumer}.
* Ready OpenSAML for use, configure it with reasonable defaults, and modify the
* {@link XMLObjectProviderRegistry} using the provided {@link Consumer}.
*
* Initialization is guaranteed to happen only once per application. This method will throw an exception
* if initialization already took place earlier in the application.
*
* @param registryConsumer the {@link Consumer} to further configure the {@link XMLObjectProviderRegistry}
* @throws Saml2Exception if initialization already happened previously or if OpenSAML failed to initialize
* Initialization is guaranteed to happen only once per application. This method will
* throw an exception if initialization already took place earlier in the application.
* @param registryConsumer the {@link Consumer} to further configure the
* {@link XMLObjectProviderRegistry}
* @throws Saml2Exception if initialization already happened previously or if OpenSAML
* failed to initialize
*/
public static void requireInitialize(Consumer<XMLObjectProviderRegistry> registryConsumer) {
if (!initialize(registryConsumer)) {
@@ -108,39 +118,39 @@ public class OpenSamlInitializationService {
private static boolean initialize(Consumer<XMLObjectProviderRegistry> registryConsumer) {
if (initialized.compareAndSet(false, true)) {
log.trace("Initializing OpenSAML");
try {
InitializationService.initialize();
} catch (Exception e) {
throw new Saml2Exception(e);
}
catch (Exception ex) {
throw new Saml2Exception(ex);
}
BasicParserPool parserPool = new BasicParserPool();
parserPool.setMaxPoolSize(50);
Map<String, Boolean> parserBuilderFeatures = new HashMap<>();
parserBuilderFeatures.put("http://apache.org/xml/features/disallow-doctype-decl", TRUE);
parserBuilderFeatures.put(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE);
parserBuilderFeatures.put("http://xml.org/sax/features/external-general-entities", FALSE);
parserBuilderFeatures.put("http://apache.org/xml/features/validation/schema/normalized-value", FALSE);
parserBuilderFeatures.put("http://xml.org/sax/features/external-parameter-entities", FALSE);
parserBuilderFeatures.put("http://apache.org/xml/features/dom/defer-node-expansion", FALSE);
parserPool.setBuilderFeatures(parserBuilderFeatures);
parserPool.setBuilderFeatures(getParserBuilderFeatures());
try {
parserPool.initialize();
} catch (Exception e) {
throw new Saml2Exception(e);
}
setParserPool(parserPool);
catch (Exception ex) {
throw new Saml2Exception(ex);
}
XMLObjectProviderRegistrySupport.setParserPool(parserPool);
registryConsumer.accept(ConfigurationService.get(XMLObjectProviderRegistry.class));
log.debug("Initialized OpenSAML");
return true;
} else {
log.debug("Refused to re-initialize OpenSAML");
return false;
}
log.debug("Refused to re-initialize OpenSAML");
return false;
}
private static Map<String, Boolean> getParserBuilderFeatures() {
Map<String, Boolean> parserBuilderFeatures = new HashMap<>();
parserBuilderFeatures.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
parserBuilderFeatures.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
parserBuilderFeatures.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
parserBuilderFeatures.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
parserBuilderFeatures.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
parserBuilderFeatures.put("http://apache.org/xml/features/dom/defer-node-expansion", Boolean.FALSE);
return parserBuilderFeatures;
}
}
@@ -25,21 +25,23 @@ import org.springframework.util.Assert;
* A representation of an SAML 2.0 Error.
*
* <p>
* 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.
* 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.
* </p>
*
* @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
*/
@@ -51,7 +53,6 @@ public class Saml2Error implements Serializable {
/**
* Returns the error code.
*
* @return the error code
*/
public final String getErrorCode() {
@@ -60,7 +61,6 @@ public class Saml2Error implements Serializable {
/**
* Returns the error description.
*
* @return the error description
*/
public final String getDescription() {
@@ -69,7 +69,7 @@ public class Saml2Error implements Serializable {
@Override
public String toString() {
return "[" + this.getErrorCode() + "] " +
(this.getDescription() != null ? this.getDescription() : "");
return "[" + this.getErrorCode() + "] " + ((this.getDescription() != null) ? this.getDescription() : "");
}
}
@@ -22,80 +22,84 @@ package org.springframework.security.saml2.core;
* @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
* 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.
* 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.
* 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.
* 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.
* 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.
* 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
* 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.
* 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.
* An error happened during validation. Used when internal, non classified, errors are
* caught during the authentication process.
*/
String INTERNAL_VALIDATION_ERROR = "internal_validation_error";
/**
* The relying party registration was not found.
* The registration ID did not correspond to any relying party registration.
* The relying party registration was not found. The registration ID did not
* correspond to any relying party registration.
*/
String RELYING_PARTY_REGISTRATION_NOT_FOUND = "relying_party_registration_not_found";
}
@@ -30,6 +30,7 @@ import org.springframework.util.Assert;
* @since 5.4
*/
public final class Saml2ResponseValidatorResult {
static final Saml2ResponseValidatorResult NO_ERRORS = new Saml2ResponseValidatorResult(Collections.emptyList());
private final Collection<Saml2Error> errors;
@@ -41,7 +42,6 @@ public final class Saml2ResponseValidatorResult {
/**
* Say whether this result indicates success
*
* @return whether this result has errors
*/
public boolean hasErrors() {
@@ -50,17 +50,16 @@ public final class Saml2ResponseValidatorResult {
/**
* Return error details regarding the validation attempt
*
* @return the collection of results in this result, if any; returns an empty list otherwise
* @return the collection of results in this result, if any; returns an empty list
* otherwise
*/
public Collection<Saml2Error> getErrors() {
return Collections.unmodifiableCollection(this.errors);
}
/**
* Return a new {@link Saml2ResponseValidatorResult} that contains
* both the given {@link Saml2Error} and the errors from the result
*
* Return a new {@link Saml2ResponseValidatorResult} that contains both the given
* {@link Saml2Error} and the errors from the result
* @param error the {@link Saml2Error} to append
* @return a new {@link Saml2ResponseValidatorResult} for further reporting
*/
@@ -72,10 +71,8 @@ public final class Saml2ResponseValidatorResult {
}
/**
* Return a new {@link Saml2ResponseValidatorResult} that contains
* the errors from the given {@link Saml2ResponseValidatorResult} as well
* as this result.
*
* Return a new {@link Saml2ResponseValidatorResult} that contains the errors from the
* given {@link Saml2ResponseValidatorResult} as well as this result.
* @param result the {@link Saml2ResponseValidatorResult} to merge with this one
* @return a new {@link Saml2ResponseValidatorResult} for further reporting
*/
@@ -88,7 +85,6 @@ public final class Saml2ResponseValidatorResult {
/**
* Construct a successful {@link Saml2ResponseValidatorResult}
*
* @return an {@link Saml2ResponseValidatorResult} with no errors
*/
public static Saml2ResponseValidatorResult success() {
@@ -97,7 +93,6 @@ public final class Saml2ResponseValidatorResult {
/**
* Construct a failure {@link Saml2ResponseValidatorResult} with the provided detail
*
* @param errors the list of errors
* @return an {@link Saml2ResponseValidatorResult} with the errors specified
*/
@@ -107,7 +102,6 @@ public final class Saml2ResponseValidatorResult {
/**
* Construct a failure {@link Saml2ResponseValidatorResult} with the provided detail
*
* @param errors the list of errors
* @return an {@link Saml2ResponseValidatorResult} with the errors specified
*/
@@ -118,4 +112,5 @@ public final class Saml2ResponseValidatorResult {
return new Saml2ResponseValidatorResult(errors);
}
}
@@ -13,50 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.core;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import org.springframework.util.Assert;
import static java.util.Arrays.asList;
import static org.springframework.util.Assert.notEmpty;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.Assert.state;
/**
* An object for holding a public certificate, any associated private key, and its intended
* <a href="https://www.oasis-open.org/committees/download.php/8958/sstc-saml-implementation-guidelines-draft-01.pdf">
* usages
* </a>
* (Line 584, Section 4.3 Credentials).
* An object for holding a public certificate, any associated private key, and its
* intended <a href=
* "https://www.oasis-open.org/committees/download.php/8958/sstc-saml-implementation-guidelines-draft-01.pdf">
* usages </a> (Line 584, Section 4.3 Credentials).
*
* @since 5.4
* @author Filip Hanik
* @author Josh Cummings
* @since 5.4
*/
public final class Saml2X509Credential {
public enum Saml2X509CredentialType {
VERIFICATION,
ENCRYPTION,
SIGNING,
DECRYPTION,
}
private final PrivateKey privateKey;
private final X509Certificate certificate;
private final Set<Saml2X509CredentialType> credentialTypes;
/**
* Creates a {@link Saml2X509Credential} using the provided parameters
*
* @param certificate the credential's public certificiate
* @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#VERIFICATION} or
* {@link Saml2X509CredentialType#ENCRYPTION} or both.
* @param types the credential's intended usages, must be one of
* {@link Saml2X509CredentialType#VERIFICATION} or
* {@link Saml2X509CredentialType#ENCRYPTION} or both.
*/
public Saml2X509Credential(X509Certificate certificate, Saml2X509CredentialType... types) {
this(null, false, certificate, types);
@@ -65,11 +57,11 @@ public final class Saml2X509Credential {
/**
* Creates a {@link Saml2X509Credential} using the provided parameters
*
* @param privateKey the credential's private key
* @param certificate the credential's public certificate
* @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#SIGNING} or
* {@link Saml2X509CredentialType#DECRYPTION} or both.
* @param types the credential's intended usages, must be one of
* {@link Saml2X509CredentialType#SIGNING} or
* {@link Saml2X509CredentialType#DECRYPTION} or both.
*/
public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Saml2X509CredentialType... types) {
this(privateKey, true, certificate, types);
@@ -78,7 +70,6 @@ public final class Saml2X509Credential {
/**
* Creates a {@link Saml2X509Credential} using the provided parameters
*
* @param privateKey the credential's private key
* @param certificate the credential's public certificate
* @param types the credential's intended usages
@@ -125,26 +116,22 @@ public final class Saml2X509Credential {
return new Saml2X509Credential(privateKey, certificate, Saml2X509Credential.Saml2X509CredentialType.SIGNING);
}
private Saml2X509Credential(
PrivateKey privateKey,
boolean keyRequired,
X509Certificate certificate,
private Saml2X509Credential(PrivateKey privateKey, boolean keyRequired, X509Certificate certificate,
Saml2X509CredentialType... types) {
notNull(certificate, "certificate cannot be null");
notEmpty(types, "credentials types cannot be empty");
Assert.notNull(certificate, "certificate cannot be null");
Assert.notEmpty(types, "credentials types cannot be empty");
if (keyRequired) {
notNull(privateKey, "privateKey cannot be null");
Assert.notNull(privateKey, "privateKey cannot be null");
}
this.privateKey = privateKey;
this.certificate = certificate;
this.credentialTypes = new LinkedHashSet<>(asList(types));
this.credentialTypes = new LinkedHashSet<>(Arrays.asList(types));
}
/**
* Get the private key for this credential
*
* @return the private key, may be null
* @see {@link #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)}
* @see #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)
*/
public PrivateKey getPrivateKey() {
return this.privateKey;
@@ -152,7 +139,6 @@ public final class Saml2X509Credential {
/**
* Get the public certificate for this credential
*
* @return the public certificate
*/
public X509Certificate getCertificate() {
@@ -161,7 +147,6 @@ public final class Saml2X509Credential {
/**
* Indicate whether this credential can be used for signing
*
* @return true if the credential has a {@link Saml2X509CredentialType#SIGNING} type
*/
public boolean isSigningCredential() {
@@ -170,8 +155,8 @@ public final class Saml2X509Credential {
/**
* Indicate whether this credential can be used for decryption
*
* @return true if the credential has a {@link Saml2X509CredentialType#DECRYPTION} type
* @return true if the credential has a {@link Saml2X509CredentialType#DECRYPTION}
* type
*/
public boolean isDecryptionCredential() {
return getCredentialTypes().contains(Saml2X509CredentialType.DECRYPTION);
@@ -179,8 +164,8 @@ public final class Saml2X509Credential {
/**
* Indicate whether this credential can be used for verification
*
* @return true if the credential has a {@link Saml2X509CredentialType#VERIFICATION} type
* @return true if the credential has a {@link Saml2X509CredentialType#VERIFICATION}
* type
*/
public boolean isVerificationCredential() {
return getCredentialTypes().contains(Saml2X509CredentialType.VERIFICATION);
@@ -188,8 +173,8 @@ public final class Saml2X509Credential {
/**
* Indicate whether this credential can be used for encryption
*
* @return true if the credential has a {@link Saml2X509CredentialType#ENCRYPTION} type
* @return true if the credential has a {@link Saml2X509CredentialType#ENCRYPTION}
* type
*/
public boolean isEncryptionCredential() {
return getCredentialTypes().contains(Saml2X509CredentialType.ENCRYPTION);
@@ -197,7 +182,6 @@ public final class Saml2X509Credential {
/**
* List all this credential's intended usages
*
* @return the set of this credential's intended usages
*/
public Set<Saml2X509CredentialType> getCredentialTypes() {
@@ -206,12 +190,15 @@ public final class Saml2X509Credential {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Saml2X509Credential that = (Saml2X509Credential) o;
return Objects.equals(this.privateKey, that.privateKey) &&
this.certificate.equals(that.certificate) &&
this.credentialTypes.equals(that.credentialTypes);
return Objects.equals(this.privateKey, that.privateKey) && this.certificate.equals(that.certificate)
&& this.credentialTypes.equals(that.credentialTypes);
}
@Override
@@ -228,7 +215,20 @@ public final class Saml2X509Credential {
break;
}
}
state(valid, () -> usage +" is not a valid usage for this credential");
Assert.state(valid, () -> usage + " is not a valid usage for this credential");
}
}
public enum Saml2X509CredentialType {
VERIFICATION,
ENCRYPTION,
SIGNING,
DECRYPTION,
}
}
@@ -13,53 +13,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.credentials;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import org.springframework.util.Assert;
import static java.util.Arrays.asList;
import static org.springframework.util.Assert.notEmpty;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.Assert.state;
/**
* Saml2X509Credential is meant to hold an X509 certificate, or an X509 certificate and a
* private key. Per:
* https://www.oasis-open.org/committees/download.php/8958/sstc-saml-implementation-guidelines-draft-01.pdf
* Line: 584, Section 4.3 Credentials Used for both signing, signature verification and encryption/decryption
* Line: 584, Section 4.3 Credentials Used for both signing, signature verification and
* encryption/decryption
*
* @since 5.2
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential} instead
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential}
* instead
*/
@Deprecated
public class Saml2X509Credential {
/**
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType} instead
*/
@Deprecated
public enum Saml2X509CredentialType {
VERIFICATION,
ENCRYPTION,
SIGNING,
DECRYPTION,
}
private final PrivateKey privateKey;
private final X509Certificate certificate;
private final Set<Saml2X509CredentialType> credentialTypes;
/**
* Creates a Saml2X509Credentials representing Identity Provider credentials for
* verification, encryption or both.
* @param certificate an IDP X509Certificate, cannot be null
* @param types credential types, must be one of {@link Saml2X509CredentialType#VERIFICATION} or
* {@link Saml2X509CredentialType#ENCRYPTION} or both.
* @param types credential types, must be one of
* {@link Saml2X509CredentialType#VERIFICATION} or
* {@link Saml2X509CredentialType#ENCRYPTION} or both.
*/
public Saml2X509Credential(X509Certificate certificate, Saml2X509CredentialType... types) {
this(null, false, certificate, types);
@@ -70,9 +62,11 @@ public class Saml2X509Credential {
* Creates a Saml2X509Credentials representing Service Provider credentials for
* signing, decryption or both.
* @param privateKey a private key used for signing or decryption, cannot be null
* @param certificate an SP X509Certificate shared with identity providers, cannot be null
* @param types credential types, must be one of {@link Saml2X509CredentialType#SIGNING} or
* {@link Saml2X509CredentialType#DECRYPTION} or both.
* @param certificate an SP X509Certificate shared with identity providers, cannot be
* null
* @param types credential types, must be one of
* {@link Saml2X509CredentialType#SIGNING} or
* {@link Saml2X509CredentialType#DECRYPTION} or both.
*/
public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Saml2X509CredentialType... types) {
this(privateKey, true, certificate, types);
@@ -87,25 +81,21 @@ public class Saml2X509Credential {
this.credentialTypes = types;
}
private Saml2X509Credential(
PrivateKey privateKey,
boolean keyRequired,
X509Certificate certificate,
private Saml2X509Credential(PrivateKey privateKey, boolean keyRequired, X509Certificate certificate,
Saml2X509CredentialType... types) {
notNull(certificate, "certificate cannot be null");
notEmpty(types, "credentials types cannot be empty");
Assert.notNull(certificate, "certificate cannot be null");
Assert.notEmpty(types, "credentials types cannot be empty");
if (keyRequired) {
notNull(privateKey, "privateKey cannot be null");
Assert.notNull(privateKey, "privateKey cannot be null");
}
this.privateKey = privateKey;
this.certificate = certificate;
this.credentialTypes = new LinkedHashSet<>(asList(types));
this.credentialTypes = new LinkedHashSet<>(Arrays.asList(types));
}
/**
* Returns true if the credential has a private key and can be used for signing, the types will contain
* {@link Saml2X509CredentialType#SIGNING}.
* Returns true if the credential has a private key and can be used for signing, the
* types will contain {@link Saml2X509CredentialType#SIGNING}.
* @return true if the credential is a {@link Saml2X509CredentialType#SIGNING} type
*/
public boolean isSigningCredential() {
@@ -113,8 +103,8 @@ public class Saml2X509Credential {
}
/**
* Returns true if the credential has a private key and can be used for decryption, the types will contain
* {@link Saml2X509CredentialType#DECRYPTION}.
* Returns true if the credential has a private key and can be used for decryption,
* the types will contain {@link Saml2X509CredentialType#DECRYPTION}.
* @return true if the credential is a {@link Saml2X509CredentialType#DECRYPTION} type
*/
public boolean isDecryptionCredential() {
@@ -122,18 +112,20 @@ public class Saml2X509Credential {
}
/**
* Returns true if the credential has a certificate and can be used for signature verification, the types will contain
* {@link Saml2X509CredentialType#VERIFICATION}.
* @return true if the credential is a {@link Saml2X509CredentialType#VERIFICATION} type
* Returns true if the credential has a certificate and can be used for signature
* verification, the types will contain {@link Saml2X509CredentialType#VERIFICATION}.
* @return true if the credential is a {@link Saml2X509CredentialType#VERIFICATION}
* type
*/
public boolean isSignatureVerficationCredential() {
return getCredentialTypes().contains(Saml2X509CredentialType.VERIFICATION);
}
/**
* Returns true if the credential has a certificate and can be used for signature verification, the types will contain
* {@link Saml2X509CredentialType#VERIFICATION}.
* @return true if the credential is a {@link Saml2X509CredentialType#VERIFICATION} type
* Returns true if the credential has a certificate and can be used for signature
* verification, the types will contain {@link Saml2X509CredentialType#VERIFICATION}.
* @return true if the credential is a {@link Saml2X509CredentialType#VERIFICATION}
* type
*/
public boolean isEncryptionCredential() {
return getCredentialTypes().contains(Saml2X509CredentialType.ENCRYPTION);
@@ -150,7 +142,7 @@ public class Saml2X509Credential {
/**
* Returns the private key, or null if this credential type doesn't require one.
* @return the private key, or null
* @see {@link #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)}
* @see #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)
*/
public PrivateKey getPrivateKey() {
return this.privateKey;
@@ -166,12 +158,15 @@ public class Saml2X509Credential {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Saml2X509Credential that = (Saml2X509Credential) o;
return Objects.equals(this.privateKey, that.privateKey) &&
this.certificate.equals(that.certificate) &&
this.credentialTypes.equals(that.credentialTypes);
return Objects.equals(this.privateKey, that.privateKey) && this.certificate.equals(that.certificate)
&& this.credentialTypes.equals(that.credentialTypes);
}
@Override
@@ -188,7 +183,26 @@ public class Saml2X509Credential {
break;
}
}
state(valid, () -> usage +" is not a valid usage for this credential");
Assert.state(valid, () -> usage + " is not a valid usage for this credential");
}
}
/**
* @deprecated Use
* {@link org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType}
* instead
*/
@Deprecated
public enum Saml2X509CredentialType {
VERIFICATION,
ENCRYPTION,
SIGNING,
DECRYPTION,
}
}
@@ -16,39 +16,41 @@
package org.springframework.security.saml2.provider.service.authentication;
import java.nio.charset.Charset;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* Data holder for {@code AuthNRequest} parameters to be sent using either the
* {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} binding.
* Data will be encoded and possibly deflated, but will not be escaped for transport,
* ie URL encoded, {@link org.springframework.web.util.UriUtils#encode(String, Charset)}
* or HTML encoded, {@link org.springframework.web.util.HtmlUtils#htmlEscape(String)}.
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
* {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} binding. Data
* will be encoded and possibly deflated, but will not be escaped for transport, ie URL
* encoded, {@link org.springframework.web.util.UriUtils#encode(String, Charset)} or HTML
* encoded, {@link org.springframework.web.util.HtmlUtils#htmlEscape(String)}.
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* (line 2031)
*
* @since 5.3
* @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext)
* @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)
* @since 5.3
*/
abstract class AbstractSaml2AuthenticationRequest {
public abstract class AbstractSaml2AuthenticationRequest {
private final String samlRequest;
private final String relayState;
private final String authenticationRequestUri;
/**
* Mandatory constructor for the {@link AbstractSaml2AuthenticationRequest}
* @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or null
* @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or
* null
* @param relayState - RelayState value that accompanies the request, may be null
* @param authenticationRequestUri - The authenticationRequestUri, a URL, where to send the XML message, cannot be empty or null
* @param authenticationRequestUri - The authenticationRequestUri, a URL, where to
* send the XML message, cannot be empty or null
*/
AbstractSaml2AuthenticationRequest(
String samlRequest,
String relayState,
String authenticationRequestUri) {
AbstractSaml2AuthenticationRequest(String samlRequest, String relayState, String authenticationRequestUri) {
Assert.hasText(samlRequest, "samlRequest cannot be null or empty");
Assert.hasText(authenticationRequestUri, "authenticationRequestUri cannot be null or empty");
this.authenticationRequestUri = authenticationRequestUri;
@@ -57,9 +59,10 @@ abstract class AbstractSaml2AuthenticationRequest {
}
/**
* Returns the AuthNRequest XML value to be sent. This value is already encoded for transport.
* If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the value is deflated and SAML encoded.
* If {@link #getBinding()} is {@link Saml2MessageBinding#POST} the value is SAML encoded.
* Returns the AuthNRequest XML value to be sent. This value is already encoded for
* transport. If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the
* value is deflated and SAML encoded. If {@link #getBinding()} is
* {@link Saml2MessageBinding#POST} the value is SAML encoded.
* @return the SAMLRequest parameter value
*/
public String getSamlRequest() {
@@ -83,8 +86,9 @@ abstract class AbstractSaml2AuthenticationRequest {
}
/**
* Returns the binding this AuthNRequest will be sent and
* encoded with. If {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be automatically applied.
* Returns the binding this AuthNRequest will be sent and encoded with. If
* {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be
* automatically applied.
* @return the binding this message will be sent with.
*/
public abstract Saml2MessageBinding getBinding();
@@ -92,9 +96,12 @@ abstract class AbstractSaml2AuthenticationRequest {
/**
* A builder for {@link AbstractSaml2AuthenticationRequest} and its subclasses.
*/
static class Builder<T extends Builder<T>> {
public static class Builder<T extends Builder<T>> {
String authenticationRequestUri;
String samlRequest;
String relayState;
protected Builder() {
@@ -109,12 +116,10 @@ abstract class AbstractSaml2AuthenticationRequest {
return (T) this;
}
/**
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
*
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the
* map.
* @param relayState the relay state value, unencoded. if null or empty, the
* parameter will be removed from the map.
* @return this object
*/
public T relayState(String relayState) {
@@ -124,7 +129,6 @@ abstract class AbstractSaml2AuthenticationRequest {
/**
* Sets the {@code SAMLRequest} parameter that will accompany this AuthNRequest
*
* @param samlRequest the SAMLRequest parameter.
* @return this object
*/
@@ -134,8 +138,8 @@ abstract class AbstractSaml2AuthenticationRequest {
}
/**
* Sets the {@code authenticationRequestUri}, a URL that will receive the AuthNRequest message
*
* Sets the {@code authenticationRequestUri}, a URL that will receive the
* AuthNRequest message
* @param authenticationRequestUri the relay state value, unencoded.
* @return this object
*/
@@ -143,6 +147,7 @@ abstract class AbstractSaml2AuthenticationRequest {
this.authenticationRequestUri = authenticationRequestUri;
return _this();
}
}
}
@@ -16,12 +16,12 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Default implementation of a {@link Saml2AuthenticatedPrincipal}.
*
@@ -31,12 +31,12 @@ import java.util.Map;
public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable {
private final String name;
private final Map<String, List<Object>> attributes;
public DefaultSaml2AuthenticatedPrincipal(String name, Map<String, List<Object>> attributes) {
Assert.notNull(name, "name cannot be null");
Assert.notNull(attributes, "attributes cannot be null");
this.name = name;
this.attributes = attributes;
}
@@ -50,4 +50,5 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri
public Map<String, List<Object>> getAttributes() {
return this.attributes;
}
}
@@ -13,6 +13,7 @@
* 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;
@@ -20,6 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -29,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.xml.namespace.QName;
@@ -55,6 +58,7 @@ import org.opensaml.saml.criterion.ProtocolCriterion;
import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
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;
@@ -97,6 +101,7 @@ 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;
@@ -113,59 +118,49 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.CLOCK_SKEW;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.COND_VALID_AUDIENCES;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SIGNATURE_REQUIRED;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.DECRYPTION_ERROR;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_ASSERTION;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_DESTINATION;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_ISSUER;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_SIGNATURE;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.MALFORMED_RESPONSE_DATA;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.SUBJECT_NOT_FOUND;
import static org.springframework.security.saml2.core.Saml2ResponseValidatorResult.failure;
import static org.springframework.security.saml2.core.Saml2ResponseValidatorResult.success;
import static org.springframework.util.Assert.notNull;
/**
* 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.
* 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).
* 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>
* <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.
* 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>
* <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.
* 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>
* <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.
* 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>
* <p>
* This provider does not perform an X509 certificate validation on the configured asserting party, IDP, verification
* certificates.
* This provider does not perform an X509 certificate validation on the configured
* asserting party, IDP, verification certificates.
* </p>
*
* @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://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>
*/
public final class OpenSamlAuthenticationProvider implements AuthenticationProvider {
@@ -177,49 +172,45 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
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 -> singletonList(new SimpleGrantedAuthority("ROLE_USER")));
private GrantedAuthoritiesMapper authoritiesMapper = (a -> a);
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, ? extends AbstractAuthenticationToken> responseAuthenticationConverter =
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);
return new Saml2Authentication(
new DefaultSaml2AuthenticatedPrincipal(username, attributes), token.getSaml2Response(),
this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion)));
};
private Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter = (
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);
return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal(username, attributes),
token.getSaml2Response(), this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion)));
};
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator =
createDefaultAssertionValidator(INVALID_SIGNATURE,
assertionToken -> {
SignatureTrustEngine engine = this.signatureTrustEngineConverter.convert(assertionToken.token);
return SAML20AssertionValidators.createSignatureValidator(engine);
},
assertionToken ->
new ValidationContext(Collections.singletonMap(SIGNATURE_REQUIRED, false))
);
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator = createDefaultAssertionValidator(
Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> {
SignatureTrustEngine engine = this.signatureTrustEngineConverter.convert(assertionToken.token);
return SAML20AssertionValidators.createSignatureValidator(engine);
}, (assertionToken) -> new ValidationContext(
Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false)));
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator =
createDefaultAssertionValidator(INVALID_ASSERTION,
assertionToken -> SAML20AssertionValidators.attributeValidator,
assertionToken -> createValidationContext(
assertionToken,
params -> params.put(CLOCK_SKEW, this.responseTimeValidationSkew.toMillis())
));
private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator = createDefaultAssertionValidator(
Saml2ErrorCodes.INVALID_ASSERTION, (assertionToken) -> SAML20AssertionValidators.attributeValidator,
(assertionToken) -> createValidationContext(assertionToken, (params) -> params
.put(SAML2AssertionValidationParameters.CLOCK_SKEW, this.responseTimeValidationSkew.toMillis())));
private Converter<Saml2AuthenticationToken, SignatureTrustEngine> signatureTrustEngineConverter = new SignatureTrustEngineConverter();
private Converter<Saml2AuthenticationToken, SignatureTrustEngine> signatureTrustEngineConverter =
new SignatureTrustEngineConverter();
private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter();
/**
* Creates an {@link OpenSamlAuthenticationProvider}
*/
@@ -231,7 +222,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
}
/**
* Set the {@link Converter} to use for validating each {@link Assertion} in the SAML 2.0 Response.
* 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 #createDefaultAssertionValidator}, like so:
@@ -259,15 +251,14 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
* }));
* </pre>
*
* Consider taking a look at {@link #createValidationContext} to see how it
* constructs a {@link ValidationContext}.
* 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
* @since 5.4
*/
@@ -280,8 +271,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
* 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:
* You can delegate to the default behavior by calling
* {@link #createDefaultResponseAuthenticationConverter()} like so:
*
* <pre>
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
@@ -296,7 +287,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
*
* This method takes precedence over {@link #setAuthoritiesExtractor(Converter)} and
* {@link #setAuthoritiesMapper(GrantedAuthoritiesMapper)}.
*
* @param responseAuthenticationConverter the {@link Converter} to use
* @since 5.4
*/
@@ -307,26 +297,28 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
}
/**
* 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
* 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) {
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
* 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) {
notNull(authoritiesMapper, "authoritiesMapper cannot be null");
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
this.authoritiesMapper = authoritiesMapper;
}
@@ -340,64 +332,56 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
this.responseTimeValidationSkew = responseTimeValidationSkew;
}
/**
* Construct a default strategy for validating each SAML 2.0 Assertion and
* associated {@link Authentication} token
*
* 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() {
public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator() {
return createDefaultAssertionValidator(INVALID_ASSERTION,
assertionToken -> SAML20AssertionValidators.attributeValidator,
assertionToken -> createValidationContext(assertionToken, params -> {}));
return createDefaultAssertionValidator(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
*
* 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
* @param contextConverter the conversion strategy to use to generate a {@link ValidationContext}
* for each assertion being validated
* @since 5.4
*/
public static Converter<AssertionToken, Saml2ResponseValidatorResult>
createDefaultAssertionValidator(Converter<AssertionToken, ValidationContext> contextConverter) {
public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator(
Converter<AssertionToken, ValidationContext> contextConverter) {
return createDefaultAssertionValidator(INVALID_ASSERTION,
assertionToken -> SAML20AssertionValidators.attributeValidator,
contextConverter);
return createDefaultAssertionValidator(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}
*
* 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 -> {
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);
return new Saml2Authentication(
new DefaultSaml2AuthenticatedPrincipal(username, attributes), token.getSaml2Response(),
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal(username, attributes),
token.getSaml2Response(), Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
};
}
/**
* @param authentication the authentication request object, must be of type
* {@link Saml2AuthenticationToken}
*
* {@link Saml2AuthenticationToken}
* @return {@link Saml2Authentication} if the assertion is valid
* @throws AuthenticationException if a validation exception occurs
*/
@@ -409,16 +393,15 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
Response response = parse(serializedResponse);
process(token, response);
return this.responseAuthenticationConverter.convert(new ResponseToken(response, token));
} catch (Saml2AuthenticationException e) {
throw e;
} catch (Exception e) {
throw authException(INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
}
catch (Saml2AuthenticationException ex) {
throw ex;
}
catch (Exception ex) {
throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean supports(Class<?> authentication) {
return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication);
@@ -430,37 +413,35 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException {
try {
Document document = this.parserPool.parse(new ByteArrayInputStream(
response.getBytes(StandardCharsets.UTF_8)));
Document document = this.parserPool
.parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8)));
Element element = document.getDocumentElement();
return (Response) this.responseUnmarshaller.unmarshall(element);
}
catch (Exception e) {
throw authException(MALFORMED_RESPONSE_DATA, e.getMessage(), e);
catch (Exception ex) {
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex);
}
}
private void process(Saml2AuthenticationToken token, Response response) {
String issuer = response.getIssuer().getValue();
if (logger.isDebugEnabled()) {
logger.debug("Processing SAML response from " + issuer);
}
logger.debug(LogMessage.format("Processing SAML response from %s", issuer));
boolean responseSigned = response.isSigned();
Saml2ResponseValidatorResult result = validateResponse(token, response);
Decrypter decrypter = this.decrypterConverter.convert(token);
List<Assertion> assertions = decryptAssertions(decrypter, response);
if (!isSigned(responseSigned, assertions)) {
throw authException(INVALID_SIGNATURE, "Either the response or one of the assertions is unsigned. " +
"Please either sign the response or all of the assertions.");
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);
}
result = result.concat(validateAssertions(token, response));
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions());
NameID nameId = decryptPrincipal(decrypter, firstAssertion);
if (nameId == null || nameId.getValue() == null) {
Saml2Error error = new Saml2Error(SUBJECT_NOT_FOUND,
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
"Assertion [" + firstAssertion.getID() + "] is missing a subject");
result = result.concat(error);
}
@@ -468,89 +449,91 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
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() + "]");
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 authException(first.getErrorCode(), first.getDescription());
} else {
throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Successfully processed SAML Response [" + response.getID() + "]");
}
}
}
private Saml2ResponseValidatorResult validateResponse
(Saml2AuthenticationToken token, Response response) {
private Saml2ResponseValidatorResult validateResponse(Saml2AuthenticationToken token, Response response) {
Collection<Saml2Error> errors = new ArrayList<>();
String issuer = response.getIssuer().getValue();
if (response.isSigned()) {
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
try {
profileValidator.validate(response.getSignature());
} catch (Exception e) {
errors.add(new Saml2Error(INVALID_SIGNATURE,
}
catch (Exception ex) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
"Invalid signature for SAML Response [" + response.getID() + "]: "));
}
try {
CriteriaSet criteriaSet = new CriteriaSet();
criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer)));
criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
criteriaSet.add(
new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
if (!this.signatureTrustEngineConverter.convert(token).validate(response.getSignature(), criteriaSet)) {
errors.add(new Saml2Error(INVALID_SIGNATURE,
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
"Invalid signature for SAML Response [" + response.getID() + "]"));
}
} catch (Exception e) {
errors.add(new Saml2Error(INVALID_SIGNATURE,
}
catch (Exception ex) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
"Invalid signature for SAML Response [" + response.getID() + "]: "));
}
}
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() + "]";
errors.add(new Saml2Error(INVALID_DESTINATION, message));
errors.add(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());
errors.add(new Saml2Error(INVALID_ISSUER, message));
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message));
}
return failure(errors);
return Saml2ResponseValidatorResult.failure(errors);
}
private List<Assertion> decryptAssertions
(Decrypter decrypter, Response response) {
private List<Assertion> decryptAssertions(Decrypter decrypter, Response response) {
List<Assertion> assertions = new ArrayList<>();
for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) {
try {
Assertion assertion = decrypter.decrypt(encryptedAssertion);
assertions.add(assertion);
} catch (DecryptionException e) {
throw authException(DECRYPTION_ERROR, e.getMessage(), e);
}
catch (DecryptionException ex) {
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
}
}
response.getAssertions().addAll(assertions);
return response.getAssertions();
}
private Saml2ResponseValidatorResult validateAssertions
(Saml2AuthenticationToken token, Response response) {
private Saml2ResponseValidatorResult validateAssertions(Saml2AuthenticationToken token, Response response) {
List<Assertion> assertions = response.getAssertions();
if (assertions.isEmpty()) {
throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response.");
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA,
"No assertions found in response.", null);
}
Saml2ResponseValidatorResult result = success();
Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success();
if (logger.isDebugEnabled()) {
logger.debug("Validating " + assertions.size() + " assertions");
}
@@ -560,25 +543,27 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
logger.trace("Validating assertion " + assertion.getID());
}
AssertionToken assertionToken = new AssertionToken(assertion, token);
result = result
.concat(this.assertionSignatureValidator.convert(assertionToken))
result = result.concat(this.assertionSignatureValidator.convert(assertionToken))
.concat(this.assertionValidator.convert(assertionToken));
}
return result;
}
private void addValidationException(Map<String, Saml2AuthenticationException> exceptions, String code,
String message, Exception cause) {
exceptions.put(code, createAuthenticationException(code, message, cause));
}
private boolean isSigned(boolean responseSigned, List<Assertion> assertions) {
if (responseSigned) {
return true;
}
for (Assertion assertion : assertions) {
if (!assertion.isSigned()) {
return false;
}
}
return true;
}
@@ -593,8 +578,9 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
NameID nameId = (NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID());
assertion.getSubject().setNameID(nameId);
return nameId;
} catch (DecryptionException e) {
throw authException(DECRYPTION_ERROR, e.getMessage(), e);
}
catch (DecryptionException ex) {
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
}
}
@@ -602,7 +588,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
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);
@@ -611,7 +596,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
}
}
attributeMap.put(attribute.getName(), attributeValues);
}
}
return attributeMap;
@@ -632,21 +616,64 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
}
if (xmlObject instanceof XSBoolean) {
XSBooleanValue xsBooleanValue = ((XSBoolean) xmlObject).getValue();
return xsBooleanValue != null ? xsBooleanValue.getValue() : null;
return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null;
}
if (xmlObject instanceof XSDateTime) {
DateTime dateTime = ((XSDateTime) xmlObject).getValue();
return dateTime != null ? Instant.ofEpochMilli(dateTime.getMillis()) : null;
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.getMillis()) : null;
}
return null;
}
private static class SignatureTrustEngineConverter implements Converter<Saml2AuthenticationToken, SignatureTrustEngine> {
private static Saml2AuthenticationException createAuthenticationException(String code, String message,
Exception cause) {
return new Saml2AuthenticationException(new Saml2Error(code, message), cause);
}
private static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator(
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 SignatureTrustEngineConverter
implements Converter<Saml2AuthenticationToken, SignatureTrustEngine> {
@Override
public SignatureTrustEngine convert(Saml2AuthenticationToken token) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getAssertingPartyDetails().getVerificationX509Credentials();
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getAssertingPartyDetails()
.getVerificationX509Credentials();
for (Saml2X509Credential key : keys) {
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
@@ -654,55 +681,20 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
return new ExplicitKeySignatureTrustEngine(
credentialsResolver,
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()
);
return new ExplicitKeySignatureTrustEngine(credentialsResolver,
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
}
}
private static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator(
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 success();
}
} catch (Exception e) {
String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s",
assertion.getID(), ((Response) assertion.getParent()).getID(),
e.getMessage());
return 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 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(COND_VALID_AUDIENCES, singleton(audience));
params.put(SC_VALID_RECIPIENTS, 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 {
@@ -733,18 +725,18 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
});
}
private static final SAML20AssertionValidator attributeValidator =
new SAML20AssertionValidator(conditions, subjects, statements, null, null) {
@Nonnull
@Override
protected ValidationResult validateSignature(Assertion token, ValidationContext context) {
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) {
return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), engine,
validator) {
@Nonnull
@Override
protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) {
@@ -763,17 +755,16 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
return ValidationResult.VALID;
}
};
}
}
private static class DecrypterConverter implements Converter<Saml2AuthenticationToken, Decrypter> {
private final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
asList(
new InlineEncryptedKeyResolver(),
new EncryptedElementTypeEncryptedKeyResolver(),
new SimpleRetrievalMethodEncryptedKeyResolver()
)
);
Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
new SimpleRetrievalMethodEncryptedKeyResolver()));
@Override
public Decrypter convert(Saml2AuthenticationToken token) {
@@ -787,31 +778,19 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
decrypter.setRootInNewDocument(true);
return decrypter;
}
}
private static Saml2Error validationError(String code, String description) {
return new Saml2Error(code, description);
}
private static Saml2AuthenticationException authException(String code, String description)
throws Saml2AuthenticationException {
return new Saml2AuthenticationException(validationError(code, description));
}
private static Saml2AuthenticationException authException(String code, String description, Exception cause)
throws Saml2AuthenticationException {
return new Saml2AuthenticationException(validationError(code, description), cause);
}
/**
* A tuple containing an OpenSAML {@link Response} and its associated authentication token.
* 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) {
@@ -826,15 +805,19 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
public Saml2AuthenticationToken getToken() {
return this.token;
}
}
/**
* A tuple containing an OpenSAML {@link Assertion} and its associated authentication 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) {
@@ -849,5 +832,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
public Saml2AuthenticationToken getToken() {
return this.token;
}
}
}
@@ -57,17 +57,14 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2R
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode;
import static org.springframework.util.StringUtils.hasText;
/**
* @since 5.2
*/
public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
static {
OpenSamlInitializationService.initialize();
}
@@ -75,19 +72,19 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
private Clock clock = Clock.systemUTC();
private AuthnRequestMarshaller marshaller;
private AuthnRequestBuilder authnRequestBuilder;
private IssuerBuilder issuerBuilder;
private Converter<Saml2AuthenticationRequestContext, String> protocolBindingResolver =
context -> {
if (context == null) {
return SAMLConstants.SAML2_POST_BINDING_URI;
}
return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
};
private Converter<Saml2AuthenticationRequestContext, String> protocolBindingResolver = (context) -> {
if (context == null) {
return SAMLConstants.SAML2_POST_BINDING_URI;
}
return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
};
private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter
= this::createAuthnRequest;
private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = this::createAuthnRequest;
/**
* Creates an {@link OpenSamlAuthenticationRequestFactory}
@@ -98,81 +95,64 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
.getMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME);
this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory()
.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
}
@Override
@Deprecated
public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(),
request.getDestination(), request.getAssertionConsumerServiceUrl(),
this.protocolBindingResolver.convert(null));
AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(), request.getDestination(),
request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null));
for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : request.getCredentials()) {
if (credential.isSigningCredential()) {
Credential cred = getSigningCredential(credential.getCertificate(), credential.getPrivateKey(), request.getIssuer());
Credential cred = getSigningCredential(credential.getCertificate(), credential.getPrivateKey(),
request.getIssuer());
return serialize(sign(authnRequest, cred));
}
}
throw new IllegalArgumentException("No signing credential provided");
}
/**
* {@inheritDoc}
*/
@Override
public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned() ?
serialize(sign(authnRequest, context.getRelyingPartyRegistration())) :
serialize(authnRequest);
String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()
? serialize(sign(authnRequest, context.getRelyingPartyRegistration())) : serialize(authnRequest);
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
.samlRequest(samlEncode(xml.getBytes(UTF_8)))
.build();
.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build();
}
/**
* {@inheritDoc}
*/
@Override
public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) {
public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
Saml2AuthenticationRequestContext context) {
AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
String xml = serialize(authnRequest);
Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
String deflatedAndEncoded = samlEncode(samlDeflate(xml));
result.samlRequest(deflatedAndEncoded)
.relayState(context.getRelayState());
String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState());
if (context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
Collection<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningX509Credentials();
Collection<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration()
.getSigningX509Credentials();
for (Saml2X509Credential credential : signingCredentials) {
Credential cred = getSigningCredential(credential.getCertificate(), credential.getPrivateKey(), "");
Map<String, String> signedParams = signQueryParameters(
cred,
deflatedAndEncoded,
Map<String, String> signedParams = signQueryParameters(cred, deflatedAndEncoded,
context.getRelayState());
return result
.samlRequest(signedParams.get("SAMLRequest"))
.relayState(signedParams.get("RelayState"))
.sigAlg(signedParams.get("SigAlg"))
.signature(signedParams.get("Signature"))
.build();
return result.samlRequest(signedParams.get("SAMLRequest")).relayState(signedParams.get("RelayState"))
.sigAlg(signedParams.get("SigAlg")).signature(signedParams.get("Signature")).build();
}
throw new Saml2Exception("No signing credential provided");
}
return result.build();
}
private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) {
return createAuthnRequest(context.getIssuer(),
context.getDestination(), context.getAssertionConsumerServiceUrl(),
this.protocolBindingResolver.convert(context));
return createAuthnRequest(context.getIssuer(), context.getDestination(),
context.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(context));
}
private AuthnRequest createAuthnRequest
(String issuer, String destination, String assertionConsumerServiceUrl, String protocolBinding) {
private AuthnRequest createAuthnRequest(String issuer, String destination, String assertionConsumerServiceUrl,
String protocolBinding) {
AuthnRequest auth = this.authnRequestBuilder.buildObject();
auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
auth.setIssueInstant(new DateTime(this.clock.millis()));
@@ -189,7 +169,6 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
/**
* Set the {@link AuthnRequest} post-processor resolver
*
* @param authenticationRequestContextConverter
* @since 5.4
*/
@@ -200,10 +179,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
}
/**
* '
* Use this {@link Clock} with {@link Instant#now()} for generating
* timestamps
*
* ' Use this {@link Clock} with {@link Instant#now()} for generating timestamps
* @param clock
*/
public void setClock(Clock clock) {
@@ -214,30 +190,30 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
/**
* Sets the {@code protocolBinding} to use when generating authentication requests.
* Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
* The IDP will be reading this value in the {@code AuthNRequest} to determine how to
* send the Response/Assertion to the ACS URL, assertion consumer service URL.
*
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} The IDP will be reading this value
* in the {@code AuthNRequest} to determine how to send the Response/Assertion to the
* ACS URL, assertion consumer service URL.
* @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
* @throws IllegalArgumentException if the protocolBinding is not valid
* @deprecated Use {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)}
* @deprecated Use
* {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)}
* instead
*/
@Deprecated
public void setProtocolBinding(String protocolBinding) {
boolean isAllowedBinding = SAMLConstants.SAML2_POST_BINDING_URI.equals(protocolBinding) ||
SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(protocolBinding);
boolean isAllowedBinding = SAMLConstants.SAML2_POST_BINDING_URI.equals(protocolBinding)
|| SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(protocolBinding);
if (!isAllowedBinding) {
throw new IllegalArgumentException("Invalid protocol binding: " + protocolBinding);
}
this.protocolBindingResolver = context -> protocolBinding;
this.protocolBindingResolver = (context) -> protocolBinding;
}
private AuthnRequest sign(AuthnRequest authnRequest, RelyingPartyRegistration relyingPartyRegistration) {
for (Saml2X509Credential credential : relyingPartyRegistration.getSigningX509Credentials()) {
Credential cred = getSigningCredential(
credential.getCertificate(), credential.getPrivateKey(), relyingPartyRegistration.getEntityId());
Credential cred = getSigningCredential(credential.getCertificate(), credential.getPrivateKey(),
relyingPartyRegistration.getEntityId());
return sign(authnRequest, cred);
}
throw new IllegalArgumentException("No signing credential provided");
@@ -252,8 +228,9 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
try {
SignatureSupport.signObject(authnRequest, parameters);
return authnRequest;
} catch (MarshallingException | SignatureException | SecurityException e) {
throw new Saml2Exception(e);
}
catch (MarshallingException | SignatureException | SecurityException ex) {
throw new Saml2Exception(ex);
}
}
@@ -264,49 +241,32 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
return cred;
}
private Map<String, String> signQueryParameters(
Credential credential,
String samlRequest,
String relayState) {
private Map<String, String> signQueryParameters(Credential credential, String samlRequest, String relayState) {
Assert.notNull(samlRequest, "samlRequest cannot be null");
String algorithmUri = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
StringBuilder queryString = new StringBuilder();
queryString
.append("SAMLRequest")
.append("=")
.append(UriUtils.encode(samlRequest, StandardCharsets.ISO_8859_1))
queryString.append("SAMLRequest").append("=").append(UriUtils.encode(samlRequest, StandardCharsets.ISO_8859_1))
.append("&");
if (hasText(relayState)) {
queryString
.append("RelayState")
.append("=")
.append(UriUtils.encode(relayState, StandardCharsets.ISO_8859_1))
.append("&");
if (StringUtils.hasText(relayState)) {
queryString.append("RelayState").append("=")
.append(UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)).append("&");
}
queryString
.append("SigAlg")
.append("=")
.append(UriUtils.encode(algorithmUri, StandardCharsets.ISO_8859_1));
queryString.append("SigAlg").append("=").append(UriUtils.encode(algorithmUri, StandardCharsets.ISO_8859_1));
try {
byte[] rawSignature = XMLSigningUtil.signWithURI(
credential,
algorithmUri,
queryString.toString().getBytes(StandardCharsets.UTF_8)
);
byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri,
queryString.toString().getBytes(StandardCharsets.UTF_8));
String b64Signature = Saml2Utils.samlEncode(rawSignature);
Map<String, String> result = new LinkedHashMap<>();
result.put("SAMLRequest", samlRequest);
if (hasText(relayState)) {
if (StringUtils.hasText(relayState)) {
result.put("RelayState", relayState);
}
result.put("SigAlg", algorithmUri);
result.put("Signature", b64Signature);
return result;
}
catch (SecurityException e) {
throw new Saml2Exception(e);
catch (SecurityException ex) {
throw new Saml2Exception(ex);
}
}
@@ -314,8 +274,10 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
try {
Element element = this.marshaller.marshall(authnRequest);
return SerializeSupport.nodeToString(element);
} catch (MarshallingException e) {
throw new Saml2Exception(e);
}
catch (MarshallingException ex) {
throw new Saml2Exception(ex);
}
}
}
@@ -16,14 +16,14 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.lang.Nullable;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.util.CollectionUtils;
/**
* Saml2 representation of an {@link AuthenticatedPrincipal}.
*
@@ -31,9 +31,9 @@ import java.util.Map;
* @since 5.2.2
*/
public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal {
/**
* Get the first value of Saml2 token attribute by name
*
* @param name the name of the attribute
* @param <A> the type of the attribute
* @return the first attribute value or {@code null} otherwise
@@ -47,7 +47,6 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal {
/**
* Get the Saml2 token attribute by name
*
* @param name the name of the attribute
* @param <A> the type of the attribute
* @return the attribute or {@code null} otherwise
@@ -60,11 +59,11 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal {
/**
* Get the Saml2 token attributes
*
* @return the Saml2 token attributes
* @since 5.4
*/
default Map<String, List<Object>> getAttributes() {
return Collections.emptyMap();
}
}
@@ -16,32 +16,32 @@
package org.springframework.security.saml2.provider.service.authentication;
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* An implementation of an {@link AbstractAuthenticationToken}
* that represents an authenticated SAML 2.0 {@link Authentication}.
* An implementation of an {@link AbstractAuthenticationToken} that represents an
* authenticated SAML 2.0 {@link Authentication}.
* <p>
* The {@link Authentication} associates valid SAML assertion
* data with a Spring Security authentication object
* The complete assertion is contained in the object in String format,
* {@link Saml2Authentication#getSaml2Response()}
* The {@link Authentication} associates valid SAML assertion data with a Spring Security
* authentication object The complete assertion is contained in the object in String
* format, {@link Saml2Authentication#getSaml2Response()}
*
* @since 5.2
* @see AbstractAuthenticationToken
*/
public class Saml2Authentication extends AbstractAuthenticationToken {
private final AuthenticatedPrincipal principal;
private final String saml2Response;
public Saml2Authentication(AuthenticatedPrincipal principal,
String saml2Response,
public Saml2Authentication(AuthenticatedPrincipal principal, String saml2Response,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
Assert.notNull(principal, "principal cannot be null");
@@ -27,23 +27,23 @@ import org.springframework.util.Assert;
* <p>
* There are a number of scenarios where an error may occur, for example:
* <ul>
* <li>The response or assertion request is missing or malformed</li>
* <li>Missing or invalid subject</li>
* <li>Missing or invalid signatures</li>
* <li>The time period validation for the assertion fails</li>
* <li>One of the assertion conditions was not met</li>
* <li>Decryption failed</li>
* <li>Unable to locate a subject identifier, commonly known as username</li>
* <li>The response or assertion request is missing or malformed</li>
* <li>Missing or invalid subject</li>
* <li>Missing or invalid signatures</li>
* <li>The time period validation for the assertion fails</li>
* <li>One of the assertion conditions was not met</li>
* <li>Decryption failed</li>
* <li>Unable to locate a subject identifier, commonly known as username</li>
* </ul>
*
* @since 5.2
*/
public class Saml2AuthenticationException extends AuthenticationException {
private Saml2Error error;
private final Saml2Error error;
/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link Saml2Error SAML 2.0 Error}
*/
public Saml2AuthenticationException(Saml2Error error) {
@@ -52,90 +52,99 @@ public class Saml2AuthenticationException extends AuthenticationException {
/**
* 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);
this(error, (cause != null) ? cause.getMessage() : error.getDescription(), 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);
this(error, message, null);
}
/**
* 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);
Assert.notNull(error, "error cannot be null");
this.error = error;
}
/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error SAML 2.0 Error}
* @deprecated Use {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error} constructor instead
* @param error the
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error
* SAML 2.0 Error}
* @deprecated Use
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error}
* constructor instead
*/
@Deprecated
public Saml2AuthenticationException(org.springframework.security.saml2.provider.service.authentication.Saml2Error error) {
public Saml2AuthenticationException(
org.springframework.security.saml2.provider.service.authentication.Saml2Error error) {
this(error, error.getDescription());
}
/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error SAML 2.0 Error}
* @param error the
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error
* SAML 2.0 Error}
* @param cause the root cause
* @deprecated Use {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error} constructor instead
* @deprecated Use
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error}
* constructor instead
*/
@Deprecated
public Saml2AuthenticationException(org.springframework.security.saml2.provider.service.authentication.Saml2Error error, Throwable cause) {
public Saml2AuthenticationException(
org.springframework.security.saml2.provider.service.authentication.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
* @deprecated Use {@link Saml2Error} constructor instead
*/
@Deprecated
public Saml2AuthenticationException(org.springframework.security.saml2.provider.service.authentication.Saml2Error error, String message) {
super(message);
this.setError(error);
public Saml2AuthenticationException(
org.springframework.security.saml2.provider.service.authentication.Saml2Error error, String message) {
this(error, message, null);
}
/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error SAML 2.0 Error}
* @param error the
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error
* SAML 2.0 Error}
* @param message the detail message
* @param cause the root cause
* @deprecated Use {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error} constructor instead
* @deprecated Use
* {@link org.springframework.security.saml2.provider.service.authentication.Saml2Error}
* constructor instead
*/
@Deprecated
public Saml2AuthenticationException(org.springframework.security.saml2.provider.service.authentication.Saml2Error error, String message, Throwable cause) {
public Saml2AuthenticationException(
org.springframework.security.saml2.provider.service.authentication.Saml2Error error, String message,
Throwable cause) {
super(message, cause);
this.setError(error);
Assert.notNull(error, "error cannot be null");
this.error = new Saml2Error(error.getErrorCode(), error.getDescription());
}
/**
* Get the associated {@link Saml2Error}
*
* @return the associated {@link Saml2Error}
*/
public Saml2Error getSaml2Error() {
@@ -144,7 +153,6 @@ public class Saml2AuthenticationException extends AuthenticationException {
/**
* Returns the {@link Saml2Error SAML 2.0 Error}.
*
* @return the {@link Saml2Error}
* @deprecated Use {@link #getSaml2Error()} instead
*/
@@ -154,20 +162,12 @@ public class Saml2AuthenticationException extends AuthenticationException {
this.error.getErrorCode(), this.error.getDescription());
}
private void setError(Saml2Error error) {
Assert.notNull(error, "error cannot be null");
this.error = error;
}
private void setError(org.springframework.security.saml2.provider.service.authentication.Saml2Error error) {
setError(new Saml2Error(error.getErrorCode(), error.getDescription()));
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Saml2AuthenticationException{");
sb.append("error=").append(error);
sb.append("error=").append(this.error);
sb.append('}');
return sb.toString();
}
}
@@ -16,33 +16,35 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.util.Assert;
/**
* Data holder for information required to send an {@code AuthNRequest}
* from the service provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
* Data holder for information required to send an {@code AuthNRequest} from the service
* provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* (line 2031)
*
* @since 5.2
* @deprecated use {@link Saml2AuthenticationRequestContext}
*/
@Deprecated
public final class Saml2AuthenticationRequest {
private final String issuer;
private final List<Saml2X509Credential> credentials;
private final String destination;
private final String assertionConsumerServiceUrl;
private Saml2AuthenticationRequest(
String issuer,
String destination,
String assertionConsumerServiceUrl,
private Saml2AuthenticationRequest(String issuer, String destination, String assertionConsumerServiceUrl,
List<Saml2X509Credential> credentials) {
Assert.hasText(issuer, "issuer cannot be null");
Assert.hasText(destination, "destination cannot be null");
@@ -58,10 +60,9 @@ public final class Saml2AuthenticationRequest {
}
}
/**
* returns the issuer, the local SP entity ID, for this authentication request.
* This property should be used to populate the {@code AuthNRequest.Issuer} XML element.
* returns the issuer, the local SP entity ID, for this authentication request. This
* property should be used to populate the {@code AuthNRequest.Issuer} XML element.
* This value typically is a URI, but can be an arbitrary string.
* @return issuer
*/
@@ -70,8 +71,9 @@ public final class Saml2AuthenticationRequest {
}
/**
* returns the destination, the WEB Single Sign On URI, for this authentication request.
* This property populates the {@code AuthNRequest#Destination} XML attribute.
* returns the destination, the WEB Single Sign On URI, for this authentication
* request. This property populates the {@code AuthNRequest#Destination} XML
* attribute.
* @return destination
*/
public String getDestination() {
@@ -79,17 +81,18 @@ public final class Saml2AuthenticationRequest {
}
/**
* Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to receive the
* assertion on. The IDP may or may not honor this request.
* This property populates the {@code AuthNRequest#AssertionConsumerServiceURL} XML attribute.
* Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to
* receive the assertion on. The IDP may or may not honor this request. This property
* populates the {@code AuthNRequest#AssertionConsumerServiceURL} XML attribute.
* @return the AssertionConsumerServiceURL value
*/
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
return this.assertionConsumerServiceUrl;
}
/**
* Returns a list of credentials that can be used to sign the {@code AuthNRequest} object
* Returns a list of credentials that can be used to sign the {@code AuthNRequest}
* object
* @return signing credentials
*/
public List<Saml2X509Credential> getCredentials() {
@@ -97,8 +100,7 @@ public final class Saml2AuthenticationRequest {
}
/**
* A builder for {@link Saml2AuthenticationRequest}.
* returns a builder object
* A builder for {@link Saml2AuthenticationRequest}. returns a builder object
*/
public static Builder builder() {
return new Builder();
@@ -106,25 +108,25 @@ public final class Saml2AuthenticationRequest {
/**
* A builder for {@link Saml2AuthenticationRequest}.
* @param context a context object to copy values from.
* returns a builder object
* @param context a context object to copy values from. returns a builder object
*/
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
return new Builder()
.assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl())
.issuer(context.getIssuer())
.destination(context.getDestination())
.credentials(c -> c.addAll(context.getRelyingPartyRegistration().getCredentials()))
;
return new Builder().assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl())
.issuer(context.getIssuer()).destination(context.getDestination())
.credentials((c) -> c.addAll(context.getRelyingPartyRegistration().getCredentials()));
}
/**
* A builder for {@link Saml2AuthenticationRequest}.
*/
public static class Builder {
public static final class Builder {
private String issuer;
private List<Saml2X509Credential> credentials = new LinkedList<>();
private String destination;
private String assertionConsumerServiceUrl;
private Builder() {
@@ -141,14 +143,12 @@ public final class Saml2AuthenticationRequest {
}
/**
* Modifies the collection of {@link Saml2X509Credential} credentials
* used in communication between IDP and SP, specifically signing the
* authentication request.
* For example:
* <code>
* Modifies the collection of {@link Saml2X509Credential} credentials used in
* communication between IDP and SP, specifically signing the authentication
* request. For example: <code>
* Saml2X509Credential credential = ...;
* return Saml2AuthenticationRequest.withLocalSpEntityId("id")
* .credentials(c -> c.add(credential))
* .credentials((c) -> c.add(credential))
* ...
* .build();
* </code>
@@ -161,7 +161,8 @@ public final class Saml2AuthenticationRequest {
}
/**
* Sets the Destination for the authentication request. Typically the {@code Service Provider EntityID}
* Sets the Destination for the authentication request. Typically the
* {@code Service Provider EntityID}
* @param destination - a required value
* @return this {@code Builder}
*/
@@ -184,15 +185,13 @@ public final class Saml2AuthenticationRequest {
/**
* Creates a {@link Saml2AuthenticationRequest} object.
* @return the Saml2AuthenticationRequest object
* @throws {@link IllegalArgumentException} if a required property is not set
* @throws IllegalArgumentException if a required property is not set
*/
public Saml2AuthenticationRequest build() {
return new Saml2AuthenticationRequest(
this.issuer,
this.destination,
this.assertionConsumerServiceUrl,
this.credentials
);
return new Saml2AuthenticationRequest(this.issuer, this.destination, this.assertionConsumerServiceUrl,
this.credentials);
}
}
}
@@ -20,26 +20,27 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.util.Assert;
/**
* Data holder for information required to create an {@code AuthNRequest}
* to be sent from the service provider to the identity provider
* <a href="https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf">
* Data holder for information required to create an {@code AuthNRequest} to be sent from
* the service provider to the identity provider <a href=
* "https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf">
* Assertions and Protocols for SAML 2 (line 2031)</a>
*
* @since 5.3
* @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext)
* @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)
* @since 5.3
*/
public class Saml2AuthenticationRequestContext {
private final RelyingPartyRegistration relyingPartyRegistration;
private final String issuer;
private final String assertionConsumerServiceUrl;
private final String relayState;
protected Saml2AuthenticationRequestContext(
RelyingPartyRegistration relyingPartyRegistration,
String issuer,
String assertionConsumerServiceUrl,
String relayState) {
protected Saml2AuthenticationRequestContext(RelyingPartyRegistration relyingPartyRegistration, String issuer,
String assertionConsumerServiceUrl, String relayState) {
Assert.hasText(issuer, "issuer cannot be null or empty");
Assert.notNull(relyingPartyRegistration, "relyingPartyRegistration cannot be null");
Assert.hasText(assertionConsumerServiceUrl, "spAssertionConsumerServiceUrl cannot be null or empty");
@@ -50,7 +51,8 @@ public class Saml2AuthenticationRequestContext {
}
/**
* Returns the {@link RelyingPartyRegistration} configuration for which the AuthNRequest is intended for.
* Returns the {@link RelyingPartyRegistration} configuration for which the
* AuthNRequest is intended for.
* @return the {@link RelyingPartyRegistration} configuration
*/
public RelyingPartyRegistration getRelyingPartyRegistration() {
@@ -59,8 +61,8 @@ public class Saml2AuthenticationRequestContext {
/**
* Returns the {@code Issuer} value to be used in the {@code AuthNRequest} object.
* This property should be used to populate the {@code AuthNRequest.Issuer} XML element.
* This value typically is a URI, but can be an arbitrary string.
* This property should be used to populate the {@code AuthNRequest.Issuer} XML
* element. This value typically is a URI, but can be an arbitrary string.
* @return the Issuer value
*/
public String getIssuer() {
@@ -68,13 +70,13 @@ public class Saml2AuthenticationRequestContext {
}
/**
* Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to receive the
* assertion on. The IDP may or may not honor this request.
* This property populates the {@code AuthNRequest.AssertionConsumerServiceURL} XML attribute.
* Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to
* receive the assertion on. The IDP may or may not honor this request. This property
* populates the {@code AuthNRequest.AssertionConsumerServiceURL} XML attribute.
* @return the AssertionConsumerServiceURL value
*/
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
return this.assertionConsumerServiceUrl;
}
/**
@@ -86,8 +88,9 @@ public class Saml2AuthenticationRequestContext {
}
/**
* Returns the {@code Destination}, the WEB Single Sign On URI, for this authentication request.
* This property can also populate the {@code AuthNRequest.Destination} XML attribute.
* Returns the {@code Destination}, the WEB Single Sign On URI, for this
* authentication request. This property can also populate the
* {@code AuthNRequest.Destination} XML attribute.
* @return the Destination value
*/
public String getDestination() {
@@ -105,10 +108,14 @@ public class Saml2AuthenticationRequestContext {
/**
* A builder for {@link Saml2AuthenticationRequestContext}.
*/
public static class Builder {
public static final class Builder {
private String issuer;
private String assertionConsumerServiceUrl;
private String relayState;
private RelyingPartyRegistration relyingPartyRegistration;
private Builder() {
@@ -125,7 +132,8 @@ public class Saml2AuthenticationRequestContext {
}
/**
* Sets the {@link RelyingPartyRegistration} used to build the authentication request.
* Sets the {@link RelyingPartyRegistration} used to build the authentication
* request.
* @param relyingPartyRegistration - a required value
* @return this {@code Builder}
*/
@@ -147,7 +155,8 @@ public class Saml2AuthenticationRequestContext {
/**
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the map.
* @param relayState the relay state value, unencoded. if null or empty, the
* parameter will be removed from the map.
* @return this object
*/
public Builder relayState(String relayState) {
@@ -158,15 +167,13 @@ public class Saml2AuthenticationRequestContext {
/**
* Creates a {@link Saml2AuthenticationRequestContext} object.
* @return the Saml2AuthenticationRequest object
* @throws {@link IllegalArgumentException} if a required property is not set
* @throws IllegalArgumentException if a required property is not set
*/
public Saml2AuthenticationRequestContext build() {
return new Saml2AuthenticationRequestContext(
this.relyingPartyRegistration,
this.issuer,
this.assertionConsumerServiceUrl,
this.relayState
);
return new Saml2AuthenticationRequestContext(this.relyingPartyRegistration, this.issuer,
this.assertionConsumerServiceUrl, this.relayState);
}
}
}
@@ -22,14 +22,10 @@ import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest.withAuthenticationRequestContext;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode;
/**
* Component that generates AuthenticationRequest, <code>samlp:AuthnRequestType</code> XML, and accompanying
* signature data.
* as defined by https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* Component that generates AuthenticationRequest, <code>samlp:AuthnRequestType</code>
* XML, and accompanying signature data. as defined by
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* Page 50, Line 2147
*
* @since 5.2
@@ -37,81 +33,83 @@ import static org.springframework.security.saml2.provider.service.authentication
public interface Saml2AuthenticationRequestFactory {
/**
* Creates an authentication request from the Service Provider, sp, to the Identity Provider, idp.
* The authentication result is an XML string that may be signed, encrypted, both or neither.
* This method only returns the {@code SAMLRequest} string for the request, and for a complete
* set of data parameters please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)}
* or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)}
*
* @param request information about the identity provider,
* the recipient of this authentication request and accompanying data
* @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted with the
* signature embedded in the XML or neither signed and encrypted
* Creates an authentication request from the Service Provider, sp, to the Identity
* Provider, idp. The authentication result is an XML string that may be signed,
* encrypted, both or neither. This method only returns the {@code SAMLRequest} string
* for the request, and for a complete set of data parameters please use
* {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)} or
* {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)}
* @param request information about the identity provider, the recipient of this
* authentication request and accompanying data
* @return XML data in the format of a String. This data may be signed, encrypted,
* both signed and encrypted with the signature embedded in the XML or neither signed
* and encrypted
* @throws Saml2Exception when a SAML library exception occurs
* @since 5.2
* @deprecated please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)}
* or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)}
* This method will be removed in future versions of Spring Security
* @deprecated please use
* {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)} or
* {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)} This
* method will be removed in future versions of Spring Security
*/
@Deprecated
String createAuthenticationRequest(Saml2AuthenticationRequest request);
/**
* Creates all the necessary AuthNRequest parameters for a REDIRECT binding.
* If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials
* the result will not contain any signatures.
* The data set will be signed and encoded for REDIRECT binding including the DEFLATE encoding.
* It will contain the following parameters to be sent as part of the query string:
* {@code SAMLRequest, RelayState, SigAlg, Signature}.
* <i>The default implementation, for sake of backwards compatibility, of this method returns the
* SAMLRequest message with an XML signature embedded, that should only be used for the{@link Saml2MessageBinding#POST}
* binding, but works over {@link Saml2MessageBinding#POST} with most providers.</i>
* @param context - information about the identity provider, the recipient of this authentication request and
* accompanying data
* @return a {@link Saml2RedirectAuthenticationRequest} object with applicable http parameters
* necessary to make the AuthNRequest over a POST or REDIRECT binding.
* All parameters will be SAML encoded/deflated, but escaped, ie URI encoded or encoded for Form Data.
* Creates all the necessary AuthNRequest parameters for a REDIRECT binding. If the
* {@link Saml2AuthenticationRequestContext} doesn't contain any
* {@link Saml2X509CredentialType#SIGNING} credentials the result will not contain any
* signatures. The data set will be signed and encoded for REDIRECT binding including
* the DEFLATE encoding. It will contain the following parameters to be sent as part
* of the query string: {@code SAMLRequest, RelayState, SigAlg, Signature}. <i>The
* default implementation, for sake of backwards compatibility, of this method returns
* the SAMLRequest message with an XML signature embedded, that should only be used
* for the{@link Saml2MessageBinding#POST} binding, but works over
* {@link Saml2MessageBinding#POST} with most providers.</i>
* @param context - information about the identity provider, the recipient of this
* authentication request and accompanying data
* @return a {@link Saml2RedirectAuthenticationRequest} object with applicable http
* parameters necessary to make the AuthNRequest over a POST or REDIRECT binding. All
* parameters will be SAML encoded/deflated, but escaped, ie URI encoded or encoded
* for Form Data.
* @throws Saml2Exception when a SAML library exception occurs
* @since 5.3
*/
default Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
Saml2AuthenticationRequestContext context
) {
//backwards compatible with 5.2.x settings
Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context);
Saml2AuthenticationRequestContext context) {
// backwards compatible with 5.2.x settings
Saml2AuthenticationRequest.Builder resultBuilder = Saml2AuthenticationRequest
.withAuthenticationRequestContext(context);
String samlRequest = createAuthenticationRequest(resultBuilder.build());
samlRequest = samlEncode(samlDeflate(samlRequest));
return Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context)
.samlRequest(samlRequest)
samlRequest = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(samlRequest));
return Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest(samlRequest)
.build();
}
/**
* Creates all the necessary AuthNRequest parameters for a POST binding.
* If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials
* the result will not contain any signatures.
* The data set will be signed and encoded for POST binding and if applicable signed with XML signatures.
* will contain the following parameters to be sent as part of the form data: {@code SAMLRequest, RelayState}.
* <i>The default implementation of this method returns the SAMLRequest message with an XML signature embedded,
* that should only be used for the {@link Saml2MessageBinding#POST} binding.</i>
* @param context - information about the identity provider, the recipient of this authentication request and
* accompanying data
* @return a {@link Saml2PostAuthenticationRequest} object with applicable http parameters
* necessary to make the AuthNRequest over a POST binding.
* All parameters will be SAML encoded but not escaped for Form Data.
* Creates all the necessary AuthNRequest parameters for a POST binding. If the
* {@link Saml2AuthenticationRequestContext} doesn't contain any
* {@link Saml2X509CredentialType#SIGNING} credentials the result will not contain any
* signatures. The data set will be signed and encoded for POST binding and if
* applicable signed with XML signatures. will contain the following parameters to be
* sent as part of the form data: {@code SAMLRequest, RelayState}. <i>The default
* implementation of this method returns the SAMLRequest message with an XML signature
* embedded, that should only be used for the {@link Saml2MessageBinding#POST}
* binding.</i>
* @param context - information about the identity provider, the recipient of this
* authentication request and accompanying data
* @return a {@link Saml2PostAuthenticationRequest} object with applicable http
* parameters necessary to make the AuthNRequest over a POST binding. All parameters
* will be SAML encoded but not escaped for Form Data.
* @throws Saml2Exception when a SAML library exception occurs
* @since 5.3
*/
default Saml2PostAuthenticationRequest createPostAuthenticationRequest(
Saml2AuthenticationRequestContext context
) {
//backwards compatible with 5.2.x settings
Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context);
default Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
// backwards compatible with 5.2.x settings
Saml2AuthenticationRequest.Builder resultBuilder = Saml2AuthenticationRequest
.withAuthenticationRequestContext(context);
String samlRequest = createAuthenticationRequest(resultBuilder.build());
samlRequest = samlEncode(samlRequest.getBytes(StandardCharsets.UTF_8));
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
.samlRequest(samlRequest)
samlRequest = Saml2Utils.samlEncode(samlRequest.getBytes(StandardCharsets.UTF_8));
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest(samlRequest)
.build();
}
@@ -24,37 +24,34 @@ import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.util.Assert;
import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRegistrationId;
/**
* Represents an incoming SAML 2.0 response containing an assertion that has not been validated.
* {@link Saml2AuthenticationToken#isAuthenticated()} will always return false.
* Represents an incoming SAML 2.0 response containing an assertion that has not been
* validated. {@link Saml2AuthenticationToken#isAuthenticated()} will always return false.
*
* @since 5.2
* @author Filip Hanik
* @author Josh Cummings
* @since 5.2
*/
public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
private final RelyingPartyRegistration relyingPartyRegistration;
private final String saml2Response;
/**
* Creates a {@link Saml2AuthenticationToken} with the provided parameters
*
* Note that the given {@link RelyingPartyRegistration} should have all its
* templates resolved at this point. See
* Note that the given {@link RelyingPartyRegistration} should have all its templates
* resolved at this point. See
* {@link org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter}
* for an example of performing that resolution.
*
* @param relyingPartyRegistration the resolved {@link RelyingPartyRegistration} to use
* @param relyingPartyRegistration the resolved {@link RelyingPartyRegistration} to
* use
* @param saml2Response the SAML 2.0 response to authenticate
*
* @since 5.4
*/
public Saml2AuthenticationToken(RelyingPartyRegistration relyingPartyRegistration,
String saml2Response) {
public Saml2AuthenticationToken(RelyingPartyRegistration relyingPartyRegistration, String saml2Response) {
super(Collections.emptyList());
Assert.notNull(relyingPartyRegistration, "relyingPartyRegistration cannot be null");
Assert.notNull(saml2Response, "saml2Response cannot be null");
@@ -65,26 +62,23 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Creates an authentication token from an incoming SAML 2 Response object
* @param saml2Response inflated and decoded XML representation of the SAML 2 Response
* @param recipientUri the URL that the SAML 2 Response was received at. Used for validation
* @param recipientUri the URL that the SAML 2 Response was received at. Used for
* validation
* @param idpEntityId the entity ID of the asserting entity
* @param localSpEntityId the configured local SP, the relying party, entity ID
* @param credentials the credentials configured for signature verification and decryption
* @deprecated Use {@link Saml2AuthenticationToken(RelyingPartyRegistration, String)} instead
* @param credentials the credentials configured for signature verification and
* decryption
* @deprecated Use {@link #Saml2AuthenticationToken(RelyingPartyRegistration, String)}
* instead
*/
@Deprecated
public Saml2AuthenticationToken(String saml2Response,
String recipientUri,
String idpEntityId,
String localSpEntityId,
List<Saml2X509Credential> credentials) {
public Saml2AuthenticationToken(String saml2Response, String recipientUri, String idpEntityId,
String localSpEntityId, List<Saml2X509Credential> credentials) {
super(null);
this.relyingPartyRegistration = withRegistrationId(idpEntityId)
.entityId(localSpEntityId)
.assertionConsumerServiceLocation(recipientUri)
.credentials(c -> c.addAll(credentials))
.assertingPartyDetails(assertingParty -> assertingParty
.entityId(idpEntityId)
.singleSignOnServiceLocation(idpEntityId))
this.relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId(idpEntityId)
.entityId(localSpEntityId).assertionConsumerServiceLocation(recipientUri)
.credentials((c) -> c.addAll(credentials)).assertingPartyDetails((assertingParty) -> assertingParty
.entityId(idpEntityId).singleSignOnServiceLocation(idpEntityId))
.build();
this.saml2Response = saml2Response;
}
@@ -109,7 +103,6 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Get the resolved {@link RelyingPartyRegistration} associated with the request
*
* @return the resolved {@link RelyingPartyRegistration}
* @since 5.4
*/
@@ -128,7 +121,8 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Returns the URI that the SAML 2 Response object came in on
* @return URI as a string
* @deprecated Use {@link #getRelyingPartyRegistration().getAssertionConsumerServiceLocation()} instead
* @deprecated Use
* {@code getRelyingPartyRegistration().getAssertionConsumerServiceLocation()} instead
*/
@Deprecated
public String getRecipientUri() {
@@ -138,7 +132,7 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Returns the configured entity ID of the receiving relying party, SP
* @return an entityID for the configured local relying party
* @deprecated Use {@link #getRelyingPartyRegistration().getEntityId()} instead
* @deprecated Use {@code getRelyingPartyRegistration().getEntityId()} instead
*/
@Deprecated
public String getLocalSpEntityId() {
@@ -147,8 +141,9 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Returns all the credentials associated with the relying party configuraiton
* @return
* @deprecated Get the credentials through {@link #getRelyingPartyRegistration()} instead
* @return all associated credentials
* @deprecated Get the credentials through {@link #getRelyingPartyRegistration()}
* instead
*/
@Deprecated
public List<Saml2X509Credential> getX509Credentials() {
@@ -166,7 +161,6 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* The state of this object cannot be changed. Will always throw an exception
* @param authenticated ignored
* @throws {@link IllegalArgumentException}
*/
@Override
public void setAuthenticated(boolean authenticated) {
@@ -176,10 +170,13 @@ public class Saml2AuthenticationToken extends AbstractAuthenticationToken {
/**
* Returns the configured IDP, asserting party, entity ID
* @return a string representing the entity ID
* @deprecated Use {@link #getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId()} instead
* @deprecated Use
* {@code getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId()}
* instead
*/
@Deprecated
public String getIdpEntityId() {
return this.relyingPartyRegistration.getAssertingPartyDetails().getEntityId();
}
}
@@ -24,22 +24,23 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
* A representation of an SAML 2.0 Error.
*
* <p>
* 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.
* 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.
* </p>
*
* @since 5.2
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2Error} instead
*/
@Deprecated
public class Saml2Error implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final org.springframework.security.saml2.core.Saml2Error error;
/**
* Constructs a {@code Saml2Error} using the provided parameters.
*
* @param errorCode the error code
* @param description the error description
*/
@@ -49,7 +50,6 @@ public class Saml2Error implements Serializable {
/**
* Returns the error code.
*
* @return the error code
*/
public final String getErrorCode() {
@@ -58,7 +58,6 @@ public class Saml2Error implements Serializable {
/**
* Returns the error description.
*
* @return the error description
*/
public final String getDescription() {
@@ -67,7 +66,7 @@ public class Saml2Error implements Serializable {
@Override
public String toString() {
return "[" + this.getErrorCode() + "] " +
(this.getDescription() != null ? this.getDescription() : "");
return "[" + this.getErrorCode() + "] " + ((this.getDescription() != null) ? this.getDescription() : "");
}
}
@@ -24,80 +24,84 @@ package org.springframework.security.saml2.provider.service.authentication;
*/
@Deprecated
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
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS;
/**
* The response data is malformed or incomplete.
* An invalid XML object was received, and XML unmarshalling failed.
* The response data is malformed or incomplete. An invalid XML object was received,
* and XML unmarshalling failed.
*/
String MALFORMED_RESPONSE_DATA = org.springframework.security.saml2.core.Saml2ErrorCodes.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.
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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.
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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.
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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.
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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.
* 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 = org.springframework.security.saml2.core.Saml2ErrorCodes.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 = org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_ISSUER;
/**
* An error happened during validation.
* Used when internal, non classified, errors are caught during the
* authentication process.
* An error happened during validation. Used when internal, non classified, errors are
* caught during the authentication process.
*/
String INTERNAL_VALIDATION_ERROR = org.springframework.security.saml2.core.Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR;
/**
* The relying party registration was not found.
* The registration ID did not correspond to any relying party registration.
* The relying party registration was not found. The registration ID did not
* correspond to any relying party registration.
*/
String RELYING_PARTY_REGISTRATION_NOT_FOUND = org.springframework.security.saml2.core.Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND;
}
@@ -18,22 +18,18 @@ package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
/**
* Data holder for information required to send an {@code AuthNRequest} over a POST binding
* from the service provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
* Data holder for information required to send an {@code AuthNRequest} over a POST
* binding from the service provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* (line 2031)
*
* @see Saml2AuthenticationRequestFactory
* @since 5.3
* @see Saml2AuthenticationRequestFactory
*/
public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationRequest {
private Saml2PostAuthenticationRequest(
String samlRequest,
String relayState,
String authenticationRequestUri) {
Saml2PostAuthenticationRequest(String samlRequest, String relayState, String authenticationRequestUri) {
super(samlRequest, relayState, authenticationRequestUri);
}
@@ -42,30 +38,28 @@ public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationR
*/
@Override
public Saml2MessageBinding getBinding() {
return POST;
return Saml2MessageBinding.POST;
}
/**
* Constructs a {@link Builder} from a {@link Saml2AuthenticationRequestContext} object.
* By default the {@link Saml2PostAuthenticationRequest#getAuthenticationRequestUri()} will be set to the
* {@link Saml2AuthenticationRequestContext#getDestination()} value.
* @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects.
* Constructs a {@link Builder} from a {@link Saml2AuthenticationRequestContext}
* object. By default the
* {@link Saml2PostAuthenticationRequest#getAuthenticationRequestUri()} will be set to
* the {@link Saml2AuthenticationRequestContext#getDestination()} value.
* @param context input providing {@code Destination}, {@code RelayState}, and
* {@code Issuer} objects.
* @return a modifiable builder object
*/
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
return new Builder()
.authenticationRequestUri(context.getDestination())
.relayState(context.getRelayState())
;
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
}
/**
* Builder class for a {@link Saml2PostAuthenticationRequest} object.
*/
public static class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
public static final class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
private Builder() {
super();
}
/**
@@ -73,13 +67,9 @@ public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationR
* @return an immutable {@link Saml2PostAuthenticationRequest} object.
*/
public Saml2PostAuthenticationRequest build() {
return new Saml2PostAuthenticationRequest(
this.samlRequest,
this.relayState,
this.authenticationRequestUri
);
return new Saml2PostAuthenticationRequest(this.samlRequest, this.relayState, this.authenticationRequestUri);
}
}
}
@@ -18,26 +18,22 @@ package org.springframework.security.saml2.provider.service.authentication;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
/**
* Data holder for information required to send an {@code AuthNRequest} over a REDIRECT binding
* from the service provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
* Data holder for information required to send an {@code AuthNRequest} over a REDIRECT
* binding from the service provider to the identity provider
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
* (line 2031)
*
* @see Saml2AuthenticationRequestFactory
* @since 5.3
* @see Saml2AuthenticationRequestFactory
*/
public class Saml2RedirectAuthenticationRequest extends AbstractSaml2AuthenticationRequest {
public final class Saml2RedirectAuthenticationRequest extends AbstractSaml2AuthenticationRequest {
private final String sigAlg;
private final String signature;
private Saml2RedirectAuthenticationRequest(
String samlRequest,
String sigAlg,
String signature,
String relayState,
private Saml2RedirectAuthenticationRequest(String samlRequest, String sigAlg, String signature, String relayState,
String authenticationRequestUri) {
super(samlRequest, relayState, authenticationRequestUri);
this.sigAlg = sigAlg;
@@ -61,36 +57,36 @@ public class Saml2RedirectAuthenticationRequest extends AbstractSaml2Authenticat
}
/**
* @return {@link Saml2MessageBinding#REDIRECT}
* @return {@link Saml2MessageBinding#REDIRECT}
*/
@Override
public Saml2MessageBinding getBinding() {
return REDIRECT;
return Saml2MessageBinding.REDIRECT;
}
/**
* Constructs a {@link Saml2RedirectAuthenticationRequest.Builder} from a {@link Saml2AuthenticationRequestContext} object.
* By default the {@link Saml2RedirectAuthenticationRequest#getAuthenticationRequestUri()} will be set to the
* {@link Saml2AuthenticationRequestContext#getDestination()} value.
* @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects.
* Constructs a {@link Saml2RedirectAuthenticationRequest.Builder} from a
* {@link Saml2AuthenticationRequestContext} object. By default the
* {@link Saml2RedirectAuthenticationRequest#getAuthenticationRequestUri()} will be
* set to the {@link Saml2AuthenticationRequestContext#getDestination()} value.
* @param context input providing {@code Destination}, {@code RelayState}, and
* {@code Issuer} objects.
* @return a modifiable builder object
*/
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
return new Builder()
.authenticationRequestUri(context.getDestination())
.relayState(context.getRelayState())
;
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
}
/**
* Builder class for a {@link Saml2RedirectAuthenticationRequest} object.
*/
public static class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
public static final class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
private String sigAlg;
private String signature;
private Builder() {
super();
}
/**
@@ -118,16 +114,10 @@ public class Saml2RedirectAuthenticationRequest extends AbstractSaml2Authenticat
* @return an immutable {@link Saml2RedirectAuthenticationRequest} object.
*/
public Saml2RedirectAuthenticationRequest build() {
return new Saml2RedirectAuthenticationRequest(
this.samlRequest,
this.sigAlg,
this.signature,
this.relayState,
this.authenticationRequestUri
);
return new Saml2RedirectAuthenticationRequest(this.samlRequest, this.sigAlg, this.signature,
this.relayState, this.authenticationRequestUri);
}
}
}
@@ -16,26 +16,27 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.zip.Deflater.DEFLATED;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
/**
* @since 5.3
*/
final class Saml2Utils {
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private static Base64 BASE64 = new Base64(0, new byte[]{'\n'});
private Saml2Utils() {
}
static String samlEncode(byte[] b) {
return BASE64.encodeAsString(b);
@@ -48,13 +49,13 @@ final class Saml2Utils {
static byte[] samlDeflate(String s) {
try {
ByteArrayOutputStream b = new ByteArrayOutputStream();
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true));
deflater.write(s.getBytes(UTF_8));
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(Deflater.DEFLATED, true));
deflater.write(s.getBytes(StandardCharsets.UTF_8));
deflater.finish();
return b.toByteArray();
}
catch (IOException e) {
throw new Saml2Exception("Unable to deflate string", e);
catch (IOException ex) {
throw new Saml2Exception("Unable to deflate string", ex);
}
}
@@ -64,10 +65,11 @@ final class Saml2Utils {
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), UTF_8);
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
catch (IOException e) {
throw new Saml2Exception("Unable to inflate string", e);
catch (IOException ex) {
throw new Saml2Exception("Unable to inflate string", ex);
}
}
}
@@ -21,10 +21,12 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import javax.xml.namespace.QName;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.opensaml.core.xml.XMLObjectBuilder;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
@@ -43,18 +45,16 @@ import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.util.Assert;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getMarshallerFactory;
/**
* Resolves the SAML 2.0 Relying Party Metadata for a given {@link RelyingPartyRegistration}
* using the OpenSAML API.
* Resolves the SAML 2.0 Relying Party Metadata for a given
* {@link RelyingPartyRegistration} using the OpenSAML API.
*
* @author Jakub Kubrynski
* @author Josh Cummings
* @since 5.4
*/
public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
static {
OpenSamlInitializationService.initialize();
}
@@ -62,22 +62,17 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
private final EntityDescriptorMarshaller entityDescriptorMarshaller;
public OpenSamlMetadataResolver() {
this.entityDescriptorMarshaller = (EntityDescriptorMarshaller)
getMarshallerFactory().getMarshaller(EntityDescriptor.DEFAULT_ELEMENT_NAME);
this.entityDescriptorMarshaller = (EntityDescriptorMarshaller) XMLObjectProviderRegistrySupport
.getMarshallerFactory().getMarshaller(EntityDescriptor.DEFAULT_ELEMENT_NAME);
Assert.notNull(this.entityDescriptorMarshaller, "entityDescriptorMarshaller cannot be null");
}
/**
* {@inheritDoc}
*/
@Override
public String resolve(RelyingPartyRegistration relyingPartyRegistration) {
EntityDescriptor entityDescriptor = build(EntityDescriptor.ELEMENT_QNAME);
entityDescriptor.setEntityID(relyingPartyRegistration.getEntityId());
SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(relyingPartyRegistration);
entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor);
return serialize(entityDescriptor);
}
@@ -85,10 +80,10 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
SPSSODescriptor spSsoDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
spSsoDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
spSsoDescriptor.setWantAssertionsSigned(true);
spSsoDescriptor.getKeyDescriptors().addAll(buildKeys(
registration.getSigningX509Credentials(), UsageType.SIGNING));
spSsoDescriptor.getKeyDescriptors().addAll(buildKeys(
registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
spSsoDescriptor.getKeyDescriptors()
.addAll(buildKeys(registration.getSigningX509Credentials(), UsageType.SIGNING));
spSsoDescriptor.getKeyDescriptors()
.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
return spSsoDescriptor;
}
@@ -107,16 +102,14 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
KeyInfo keyInfo = build(KeyInfo.DEFAULT_ELEMENT_NAME);
X509Certificate x509Certificate = build(X509Certificate.DEFAULT_ELEMENT_NAME);
X509Data x509Data = build(X509Data.DEFAULT_ELEMENT_NAME);
try {
x509Certificate.setValue(new String(Base64.getEncoder().encode(certificate.getEncoded())));
} catch (CertificateEncodingException e) {
}
catch (CertificateEncodingException ex) {
throw new Saml2Exception("Cannot encode certificate " + certificate.toString());
}
x509Data.getX509Certificates().add(x509Certificate);
keyInfo.getX509Datas().add(x509Data);
keyDescriptor.setUse(usageType);
keyDescriptor.setKeyInfo(keyInfo);
return keyDescriptor;
@@ -132,20 +125,21 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
@SuppressWarnings("unchecked")
private <T> T build(QName elementName) {
XMLObjectBuilder<?> builder = getBuilderFactory().getBuilder(elementName);
XMLObjectBuilder<?> builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName);
if (builder == null) {
throw new Saml2Exception("Unable to resolve Builder for " + elementName);
}
return (T) builder.buildObject(elementName);
}
private String serialize(EntityDescriptor entityDescriptor) {
try {
Element element = this.entityDescriptorMarshaller.marshall(entityDescriptor);
return SerializeSupport.prettyPrintXML(element);
} catch (Exception e) {
throw new Saml2Exception(e);
}
catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
}
@@ -19,18 +19,20 @@ package org.springframework.security.saml2.provider.service.metadata;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
/**
* Resolves the SAML 2.0 Relying Party Metadata for a given {@link RelyingPartyRegistration}
* Resolves the SAML 2.0 Relying Party Metadata for a given
* {@link RelyingPartyRegistration}
*
* @author Jakub Kubrynski
* @author Josh Cummings
* @since 5.4
*/
public interface Saml2MetadataResolver {
/**
* Resolve the given relying party's metadata
*
* @param relyingPartyRegistration the relying party
* @return the relying party's metadata
*/
String resolve(RelyingPartyRegistration relyingPartyRegistration);
}
@@ -16,17 +16,14 @@
package org.springframework.security.saml2.provider.service.registration;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import static java.util.Arrays.asList;
import static org.springframework.util.Assert.notEmpty;
import static org.springframework.util.Assert.notNull;
import org.springframework.util.Assert;
/**
* @since 5.2
@@ -37,23 +34,22 @@ public class InMemoryRelyingPartyRegistrationRepository
private final Map<String, RelyingPartyRegistration> byRegistrationId;
public InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistration... registrations) {
this(asList(registrations));
this(Arrays.asList(registrations));
}
public InMemoryRelyingPartyRegistrationRepository(Collection<RelyingPartyRegistration> registrations) {
notEmpty(registrations, "registrations cannot be empty");
Assert.notEmpty(registrations, "registrations cannot be empty");
this.byRegistrationId = createMappingToIdentityProvider(registrations);
}
private static Map<String, RelyingPartyRegistration> createMappingToIdentityProvider(
Collection<RelyingPartyRegistration> rps
) {
Collection<RelyingPartyRegistration> rps) {
LinkedHashMap<String, RelyingPartyRegistration> result = new LinkedHashMap<>();
for (RelyingPartyRegistration rp : rps) {
notNull(rp, "relying party collection cannot contain null values");
Assert.notNull(rp, "relying party collection cannot contain null values");
String key = rp.getRegistrationId();
notNull(rp, "relying party identifier cannot be null");
Assert.isNull(result.get(key), () -> "relying party duplicate identifier '" + key+"' detected.");
Assert.notNull(rp, "relying party identifier cannot be null");
Assert.isNull(result.get(key), () -> "relying party duplicate identifier '" + key + "' detected.");
result.put(key, rp);
}
return Collections.unmodifiableMap(result);
@@ -61,7 +57,7 @@ public class InMemoryRelyingPartyRegistrationRepository
@Override
public RelyingPartyRegistration findByRegistrationId(String id) {
return this.byRegistrationId.get(id);
return this.byRegistrationId.get(id);
}
@Override
@@ -27,6 +27,7 @@ import java.util.List;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
@@ -47,19 +48,14 @@ import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2X509Credential;
import static java.lang.Boolean.TRUE;
import static org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS;
import static org.springframework.security.saml2.core.Saml2X509Credential.encryption;
import static org.springframework.security.saml2.core.Saml2X509Credential.verification;
import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRegistrationId;
/**
* An {@link HttpMessageConverter} that takes an {@code IDPSSODescriptor} in an HTTP response
* and converts it into a {@link RelyingPartyRegistration.Builder}.
* An {@link HttpMessageConverter} that takes an {@code IDPSSODescriptor} in an HTTP
* response and converts it into a {@link RelyingPartyRegistration.Builder}.
*
* The primary use case for this is constructing a {@link RelyingPartyRegistration} for inclusion in a
* {@link RelyingPartyRegistrationRepository}. To do so, you can include an instance of this converter in a
* {@link org.springframework.web.client.RestOperations} like so:
* The primary use case for this is constructing a {@link RelyingPartyRegistration} for
* inclusion in a {@link RelyingPartyRegistrationRepository}. To do so, you can include an
* instance of this converter in a {@link org.springframework.web.client.RestOperations}
* like so:
*
* <pre>
* RestOperations rest = new RestTemplate(Collections.singletonList(
@@ -69,11 +65,12 @@ import static org.springframework.security.saml2.provider.service.registration.R
* RelyingPartyRegistration registration = builder.registrationId("registration-id").build();
* </pre>
*
* Note that this will only configure the asserting party (IDP) half of the {@link RelyingPartyRegistration},
* meaning where and how to send AuthnRequests, how to verify Assertions, etc.
* Note that this will only configure the asserting party (IDP) half of the
* {@link RelyingPartyRegistration}, meaning where and how to send AuthnRequests, how to
* verify Assertions, etc.
*
* To further configure the {@link RelyingPartyRegistration} with relying party (SP) information, you may
* invoke the appropriate methods on the builder.
* To further configure the {@link RelyingPartyRegistration} with relying party (SP)
* information, you may invoke the appropriate methods on the builder.
*
* @author Josh Cummings
* @since 5.4
@@ -86,6 +83,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
}
private final EntityDescriptorUnmarshaller unmarshaller;
private final ParserPool parserPool;
/**
@@ -98,39 +96,26 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
this.parserPool = registry.getParserPool();
}
/**
* {@inheritDoc}
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return RelyingPartyRegistration.Builder.class.isAssignableFrom(clazz);
}
/**
* {@inheritDoc}
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(MediaType.APPLICATION_XML, MediaType.TEXT_XML);
}
/**
* {@inheritDoc}
*/
@Override
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
EntityDescriptor descriptor = entityDescriptor(inputMessage.getBody());
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAML20P_NS);
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
if (idpssoDescriptor == null) {
throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element");
}
@@ -140,54 +125,84 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
List<X509Certificate> certificates = certificates(keyDescriptor);
for (X509Certificate certificate : certificates) {
verification.add(verification(certificate));
verification.add(Saml2X509Credential.verification(certificate));
}
}
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
List<X509Certificate> certificates = certificates(keyDescriptor);
for (X509Certificate certificate : certificates) {
encryption.add(encryption(certificate));
encryption.add(Saml2X509Credential.encryption(certificate));
}
}
if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) {
List<X509Certificate> certificates = certificates(keyDescriptor);
for (X509Certificate certificate : certificates) {
verification.add(verification(certificate));
encryption.add(encryption(certificate));
verification.add(Saml2X509Credential.verification(certificate));
encryption.add(Saml2X509Credential.encryption(certificate));
}
}
}
if (verification.isEmpty()) {
throw new Saml2Exception("Metadata response is missing verification certificates, necessary for verifying SAML assertions");
throw new Saml2Exception(
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
}
RelyingPartyRegistration.Builder builder = withRegistrationId(descriptor.getEntityID())
.assertingPartyDetails(party -> party
.entityId(descriptor.getEntityID())
.wantAuthnRequestsSigned(TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
.verificationX509Credentials(c -> c.addAll(verification))
.encryptionX509Credentials(c -> c.addAll(encryption)));
RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(descriptor.getEntityID())
.assertingPartyDetails((party) -> party.entityId(descriptor.getEntityID())
.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
.verificationX509Credentials((c) -> c.addAll(verification))
.encryptionX509Credentials((c) -> c.addAll(encryption)));
for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
Saml2MessageBinding binding;
if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
binding = Saml2MessageBinding.POST;
} else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
}
else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
binding = Saml2MessageBinding.REDIRECT;
} else {
}
else {
continue;
}
builder.assertingPartyDetails(party -> party
.singleSignOnServiceLocation(singleSignOnService.getLocation())
.singleSignOnServiceBinding(binding));
builder.assertingPartyDetails(
(party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation())
.singleSignOnServiceBinding(binding));
return builder;
}
throw new Saml2Exception("Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
throw new Saml2Exception(
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
}
private List<Saml2X509Credential> getVerification(IDPSSODescriptor idpssoDescriptor) {
List<Saml2X509Credential> verification = new ArrayList<>();
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
List<X509Certificate> certificates = certificates(keyDescriptor);
for (X509Certificate certificate : certificates) {
verification.add(Saml2X509Credential.verification(certificate));
}
}
}
return verification;
}
private List<Saml2X509Credential> getEncryption(IDPSSODescriptor idpssoDescriptor) {
List<Saml2X509Credential> encryption = new ArrayList<>();
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
List<X509Certificate> certificates = certificates(keyDescriptor);
for (X509Certificate certificate : certificates) {
encryption.add(Saml2X509Credential.encryption(certificate));
}
}
}
return encryption;
}
private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) {
try {
return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo());
} catch (CertificateException e) {
throw new Saml2Exception(e);
}
catch (CertificateException ex) {
throw new Saml2Exception(ex);
}
}
@@ -196,13 +211,16 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
Document document = this.parserPool.parse(inputStream);
Element element = document.getDocumentElement();
return (EntityDescriptor) this.unmarshaller.unmarshall(element);
} catch (Exception e) {
throw new Saml2Exception(e);
}
catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
@Override
public void write(RelyingPartyRegistration.Builder builder, MediaType contentType, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException {
public void write(RelyingPartyRegistration.Builder builder, MediaType contentType, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
throw new HttpMessageNotWritableException("This converter cannot write a RelyingPartyRegistration.Builder");
}
}
@@ -19,15 +19,14 @@ package org.springframework.security.saml2.provider.service.registration;
/**
* A repository for {@link RelyingPartyRegistration}s
*
* @since 5.2
* @author Filip Hanik
* @since 5.2
*/
public interface RelyingPartyRegistrationRepository {
/**
* Returns the relying party registration identified by the provided {@code registrationId},
* or {@code null} if not found.
*
* Returns the relying party registration identified by the provided
* {@code registrationId}, or {@code null} if not found.
* @param registrationId the registration identifier
* @return the {@link RelyingPartyRegistration} if found, otherwise {@code null}
*/
@@ -30,12 +30,16 @@ import org.springframework.web.client.RestTemplate;
* @since 5.4
*/
public final class RelyingPartyRegistrations {
private static final RestOperations rest = new RestTemplate
(Arrays.asList(new OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter()));
private static final RestOperations rest = new RestTemplate(
Arrays.asList(new OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter()));
private RelyingPartyRegistrations() {
}
/**
* Return a {@link RelyingPartyRegistration.Builder} based off of the given
* SAML 2.0 Asserting Party (IDP) metadata.
* Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0
* Asserting Party (IDP) metadata.
*
* Note that by default the registrationId is set to be the given metadata location,
* but this will most often not be sufficient. To complete the configuration, most
@@ -48,21 +52,23 @@ public final class RelyingPartyRegistrations {
* .build();
* </pre>
*
* Also note that an {@code IDPSSODescriptor} typically only contains information about
* the asserting party. Thus, you will need to remember to still populate anything about the
* relying party, like any private keys the relying party will use for signing AuthnRequests.
*
* Also note that an {@code IDPSSODescriptor} typically only contains information
* about the asserting party. Thus, you will need to remember to still populate
* anything about the relying party, like any private keys the relying party will use
* for signing AuthnRequests.
* @param metadataLocation
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
*/
public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
try {
return rest.getForObject(metadataLocation, RelyingPartyRegistration.Builder.class);
} catch (RestClientException e) {
if (e.getCause() instanceof Saml2Exception) {
throw (Saml2Exception) e.getCause();
}
catch (RestClientException ex) {
if (ex.getCause() instanceof Saml2Exception) {
throw (Saml2Exception) ex.getCause();
}
throw new Saml2Exception(e);
throw new Saml2Exception(ex);
}
}
}
@@ -17,17 +17,17 @@
package org.springframework.security.saml2.provider.service.registration;
/**
* The type of bindings that messages are exchanged using
* Supported bindings are {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST}
* and {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}.
* In addition there is support for {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}
* with an XML signature in the message rather than query parameters.
* The type of bindings that messages are exchanged using Supported bindings are
* {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST} and
* {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}. In addition there is
* support for {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect} with an XML
* signature in the message rather than query parameters.
* @since 5.3
*/
public enum Saml2MessageBinding {
POST("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"),
REDIRECT("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
POST("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), REDIRECT(
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
private final String urn;
@@ -40,6 +40,7 @@ public enum Saml2MessageBinding {
* @return URN value representing this binding
*/
public String getUrn() {
return urn;
return this.urn;
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2002-2020 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.servlet.filter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @since 5.3
*/
final class Saml2ServletUtils {
private static final char PATH_DELIMITER = '/';
private Saml2ServletUtils() {
}
static String resolveUrlTemplate(String template, String baseUrl, RelyingPartyRegistration relyingParty) {
if (!StringUtils.hasText(template)) {
return baseUrl;
}
String entityId = relyingParty.getAssertingPartyDetails().getEntityId();
String registrationId = relyingParty.getRegistrationId();
Map<String, String> uriVariables = new HashMap<>();
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl).replaceQuery(null).fragment(null)
.build();
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", (port != -1) ? ":" + port : "");
String path = uriComponents.getPath();
if (StringUtils.hasLength(path)) {
if (path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : "");
uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : "");
return UriComponentsBuilder.fromUriString(template).buildAndExpand(uriVariables).toUriString();
}
static String getApplicationUri(HttpServletRequest request) {
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath()).replaceQuery(null).fragment(null).build();
return uriComponents.toUriString();
}
}
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
@@ -30,9 +31,7 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.util.Assert;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND;
import static org.springframework.util.StringUtils.hasText;
import org.springframework.util.StringUtils;
/**
* @since 5.2
@@ -40,12 +39,14 @@ import static org.springframework.util.StringUtils.hasText;
public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/saml2/sso/{registrationId}";
private final AuthenticationConverter authenticationConverter;
/**
* Creates a {@code Saml2WebSsoAuthenticationFilter} authentication filter that is configured
* to use the {@link #DEFAULT_FILTER_PROCESSES_URI} processing URL
* @param relyingPartyRegistrationRepository - repository of configured SAML 2 entities. Required.
* Creates a {@code Saml2WebSsoAuthenticationFilter} authentication filter that is
* configured to use the {@link #DEFAULT_FILTER_PROCESSES_URI} processing URL
* @param relyingPartyRegistrationRepository - repository of configured SAML 2
* entities. Required.
*/
public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
this(relyingPartyRegistrationRepository, DEFAULT_FILTER_PROCESSES_URI);
@@ -53,35 +54,32 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
/**
* Creates a {@code Saml2WebSsoAuthenticationFilter} authentication filter
* @param relyingPartyRegistrationRepository - repository of configured SAML 2 entities. Required.
* @param filterProcessesUrl the processing URL, must contain a {registrationId} variable. Required.
* @param relyingPartyRegistrationRepository - repository of configured SAML 2
* entities. Required.
* @param filterProcessesUrl the processing URL, must contain a {registrationId}
* variable. Required.
*/
public Saml2WebSsoAuthenticationFilter(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
String filterProcessesUrl) {
this(new Saml2AuthenticationTokenConverter
(new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)),
filterProcessesUrl);
this(new Saml2AuthenticationTokenConverter(
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)), filterProcessesUrl);
}
/**
* Creates a {@link Saml2WebSsoAuthenticationFilter} given the provided parameters
*
* @param authenticationConverter the strategy for converting an {@link HttpServletRequest}
* into an {@link Authentication}
* @param filterProcessingUrl the processing URL, must contain a {registrationId} variable
* @param authenticationConverter the strategy for converting an
* {@link HttpServletRequest} into an {@link Authentication}
* @param filterProcessingUrl the processing URL, must contain a {registrationId}
* variable
* @since 5.4
*/
public Saml2WebSsoAuthenticationFilter(
AuthenticationConverter authenticationConverter,
public Saml2WebSsoAuthenticationFilter(AuthenticationConverter authenticationConverter,
String filterProcessingUrl) {
super(filterProcessingUrl);
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
Assert.hasText(filterProcessingUrl, "filterProcessesUrl must contain a URL pattern");
Assert.isTrue(
filterProcessingUrl.contains("{registrationId}"),
"filterProcessesUrl must contain a {registrationId} match variable"
);
Assert.isTrue(filterProcessingUrl.contains("{registrationId}"),
"filterProcessesUrl must contain a {registrationId} match variable");
this.authenticationConverter = authenticationConverter;
setAllowSessionCreation(true);
setSessionAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy());
@@ -89,7 +87,8 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return (super.requiresAuthentication(request, response) && hasText(request.getParameter("SAMLResponse")));
return (super.requiresAuthentication(request, response)
&& StringUtils.hasText(request.getParameter("SAMLResponse")));
}
@Override
@@ -97,10 +96,11 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
throws AuthenticationException {
Authentication authentication = this.authenticationConverter.convert(request);
if (authentication == null) {
Saml2Error saml2Error = new Saml2Error(RELYING_PARTY_REGISTRATION_NOT_FOUND,
Saml2Error saml2Error = new Saml2Error(Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND,
"No relying party registration found");
throw new Saml2AuthenticationException(saml2Error);
}
return getAuthenticationManager().authenticate(authentication);
}
}
@@ -17,6 +17,8 @@
package org.springframework.security.saml2.provider.service.servlet.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -43,55 +45,58 @@ import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
/**
* This {@code Filter} formulates a
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf">SAML 2.0 AuthnRequest</a> (line 1968)
* and redirects to a configured asserting party.
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf">SAML 2.0
* AuthnRequest</a> (line 1968) and redirects to a configured asserting party.
*
* <p>
* It supports the
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf">HTTP-Redirect</a> (line 520)
* and
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf">HTTP-POST</a> (line 753)
* bindings.
* It supports the <a href=
* "https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf">HTTP-Redirect</a>
* (line 520) and <a href=
* "https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf">HTTP-POST</a>
* (line 753) bindings.
*
* <p>
* By default, this {@code Filter} responds to authentication requests
* at the {@code URI} {@code /oauth2/authorization/{registrationId}}.
* The {@code URI} template variable {@code {registrationId}} represents the
* {@link RelyingPartyRegistration#getRegistrationId() registration identifier} of the relying party
* that is used for initiating the authentication request.
* By default, this {@code Filter} responds to authentication requests at the {@code URI}
* {@code /oauth2/authorization/{registrationId}}. The {@code URI} template variable
* {@code {registrationId}} represents the
* {@link RelyingPartyRegistration#getRegistrationId() registration identifier} of the
* relying party that is used for initiating the authentication request.
*
* @since 5.2
* @author Filip Hanik
* @author Josh Cummings
* @since 5.2
*/
public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter {
private final Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver;
private Saml2AuthenticationRequestFactory authenticationRequestFactory;
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
/**
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the provided parameters
*
* @param relyingPartyRegistrationRepository a repository for relying party configurations
* @deprecated use the constructor that takes a {@link Saml2AuthenticationRequestFactory}
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the provided
* parameters
* @param relyingPartyRegistrationRepository a repository for relying party
* configurations
* @deprecated use the constructor that takes a
* {@link Saml2AuthenticationRequestFactory}
*/
@Deprecated
public Saml2WebSsoAuthenticationRequestFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
public Saml2WebSsoAuthenticationRequestFilter(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
this(new DefaultSaml2AuthenticationRequestContextResolver(
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)),
new org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory());
}
/**
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the provided parameters
*
* @param authenticationRequestContextResolver a strategy for formulating a {@link Saml2AuthenticationRequestContext}
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the provided
* parameters
* @param authenticationRequestContextResolver a strategy for formulating a
* {@link Saml2AuthenticationRequestContext}
* @since 5.4
*/
public Saml2WebSsoAuthenticationRequestFilter(
@@ -105,9 +110,10 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
}
/**
* Use the given {@link Saml2AuthenticationRequestFactory} for formulating the SAML 2.0 AuthnRequest
*
* @param authenticationRequestFactory the {@link Saml2AuthenticationRequestFactory} to use
* Use the given {@link Saml2AuthenticationRequestFactory} for formulating the SAML
* 2.0 AuthnRequest
* @param authenticationRequestFactory the {@link Saml2AuthenticationRequestFactory}
* to use
* @deprecated use the constructor instead
*/
@Deprecated
@@ -118,7 +124,6 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
/**
* Use the given {@link RequestMatcher} that activates this filter for a given request
*
* @param redirectMatcher the {@link RequestMatcher} to use
*/
public void setRedirectMatcher(RequestMatcher redirectMatcher) {
@@ -126,13 +131,9 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
this.redirectMatcher = redirectMatcher;
}
/**
* {@inheritDoc}
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
MatchResult matcher = this.redirectMatcher.matcher(request);
if (!matcher.isMatch()) {
filterChain.doFilter(request, response);
@@ -147,41 +148,37 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
RelyingPartyRegistration relyingParty = context.getRelyingPartyRegistration();
if (relyingParty.getAssertingPartyDetails().getSingleSignOnServiceBinding() == Saml2MessageBinding.REDIRECT) {
sendRedirect(response, context);
} else {
}
else {
sendPost(response, context);
}
}
private void sendRedirect(HttpServletResponse response, Saml2AuthenticationRequestContext context)
throws IOException {
Saml2RedirectAuthenticationRequest authenticationRequest =
this.authenticationRequestFactory.createRedirectAuthenticationRequest(context);
Saml2RedirectAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
.createRedirectAuthenticationRequest(context);
UriComponentsBuilder uriBuilder = UriComponentsBuilder
.fromUriString(authenticationRequest.getAuthenticationRequestUri());
addParameter("SAMLRequest", authenticationRequest.getSamlRequest(), uriBuilder);
addParameter("RelayState", authenticationRequest.getRelayState(), uriBuilder);
addParameter("SigAlg", authenticationRequest.getSigAlg(), uriBuilder);
addParameter("Signature", authenticationRequest.getSignature(), uriBuilder);
String redirectUrl = uriBuilder
.build(true)
.toUriString();
String redirectUrl = uriBuilder.build(true).toUriString();
response.sendRedirect(redirectUrl);
}
private void addParameter(String name, String value, UriComponentsBuilder builder) {
Assert.hasText(name, "name cannot be empty or null");
if (StringUtils.hasText(value)) {
builder.queryParam(
UriUtils.encode(name, ISO_8859_1),
UriUtils.encode(value, ISO_8859_1)
);
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
UriUtils.encode(value, StandardCharsets.ISO_8859_1));
}
}
private void sendPost(HttpServletResponse response, Saml2AuthenticationRequestContext context)
throws IOException {
Saml2PostAuthenticationRequest authenticationRequest =
this.authenticationRequestFactory.createPostAuthenticationRequest(context);
private void sendPost(HttpServletResponse response, Saml2AuthenticationRequestContext context) throws IOException {
Saml2PostAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
.createPostAuthenticationRequest(context);
String html = createSamlPostRequestFormData(authenticationRequest);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
@@ -191,42 +188,42 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
String authenticationRequestUri = authenticationRequest.getAuthenticationRequestUri();
String relayState = authenticationRequest.getRelayState();
String samlRequest = authenticationRequest.getSamlRequest();
StringBuilder postHtml = new StringBuilder()
.append("<!DOCTYPE html>\n")
.append("<html>\n")
.append(" <head>\n")
.append(" <meta charset=\"utf-8\" />\n")
.append(" </head>\n")
.append(" <body onload=\"document.forms[0].submit()\">\n")
.append(" <noscript>\n")
.append(" <p>\n")
.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n")
.append(" you must press the Continue button once to proceed.\n")
.append(" </p>\n")
.append(" </noscript>\n")
.append(" \n")
.append(" <form action=\"").append(authenticationRequestUri).append("\" method=\"post\">\n")
.append(" <div>\n")
.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"")
.append(HtmlUtils.htmlEscape(samlRequest))
.append("\"/>\n");
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body onload=\"document.forms[0].submit()\">\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(authenticationRequestUri);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(samlRequest));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
postHtml
.append(" <input type=\"hidden\" name=\"RelayState\" value=\"")
.append(HtmlUtils.htmlEscape(relayState))
.append("\"/>\n");
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
}
postHtml
.append(" </div>\n")
.append(" <noscript>\n")
.append(" <div>\n")
.append(" <input type=\"submit\" value=\"Continue\"/>\n")
.append(" </div>\n")
.append(" </noscript>\n")
.append(" </form>\n")
.append(" \n")
.append(" </body>\n")
.append("</html>");
return postHtml.toString();
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
}
}
@@ -19,11 +19,13 @@ package org.springframework.security.saml2.provider.service.web;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -31,17 +33,13 @@ import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRelyingPartyRegistration;
import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl;
import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
/**
* A {@link Converter} that resolves a {@link RelyingPartyRegistration} by extracting the
* registration id from the request, querying a {@link RelyingPartyRegistrationRepository},
* and resolving any template values.
* registration id from the request, querying a
* {@link RelyingPartyRegistrationRepository}, and resolving any template values.
*
* @since 5.4
* @author Josh Cummings
* @since 5.4
*/
public final class DefaultRelyingPartyRegistrationResolver
implements Converter<HttpServletRequest, RelyingPartyRegistration> {
@@ -49,11 +47,11 @@ public final class DefaultRelyingPartyRegistrationResolver
private static final char PATH_DELIMITER = '/';
private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
private final Converter<HttpServletRequest, String> registrationIdResolver = new RegistrationIdResolver();
public DefaultRelyingPartyRegistrationResolver
(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
public DefaultRelyingPartyRegistrationResolver(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null");
this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
}
@@ -64,66 +62,57 @@ public final class DefaultRelyingPartyRegistrationResolver
if (registrationId == null) {
return null;
}
RelyingPartyRegistration relyingPartyRegistration =
this.relyingPartyRegistrationRepository.findByRegistrationId(registrationId);
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository
.findByRegistrationId(registrationId);
if (relyingPartyRegistration == null) {
return null;
}
String applicationUri = getApplicationUri(request);
Function<String, String> templateResolver = templateResolver(applicationUri, relyingPartyRegistration);
String relyingPartyEntityId = templateResolver.apply(relyingPartyRegistration.getEntityId());
String assertionConsumerServiceLocation = templateResolver.apply(
relyingPartyRegistration.getAssertionConsumerServiceLocation());
return withRelyingPartyRegistration(relyingPartyRegistration)
.entityId(relyingPartyEntityId)
.assertionConsumerServiceLocation(assertionConsumerServiceLocation)
String assertionConsumerServiceLocation = templateResolver
.apply(relyingPartyRegistration.getAssertionConsumerServiceLocation());
return RelyingPartyRegistration.withRelyingPartyRegistration(relyingPartyRegistration)
.entityId(relyingPartyEntityId).assertionConsumerServiceLocation(assertionConsumerServiceLocation)
.build();
}
private Function<String, String> templateResolver(String applicationUri, RelyingPartyRegistration relyingParty) {
return template -> resolveUrlTemplate(template, applicationUri, relyingParty);
return (template) -> resolveUrlTemplate(template, applicationUri, relyingParty);
}
private static String resolveUrlTemplate(String template, String baseUrl, RelyingPartyRegistration relyingParty) {
String entityId = relyingParty.getAssertingPartyDetails().getEntityId();
String registrationId = relyingParty.getRegistrationId();
Map<String, String> uriVariables = new HashMap<>();
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl)
.replaceQuery(null)
.fragment(null)
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl).replaceQuery(null).fragment(null)
.build();
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", scheme == null ? "" : scheme);
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
String host = uriComponents.getHost();
uriVariables.put("baseHost", host == null ? "" : host);
uriVariables.put("baseHost", (host != null) ? host : "");
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", port == -1 ? "" : ":" + port);
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
String path = uriComponents.getPath();
if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
uriVariables.put("basePath", path == null ? "" : path);
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : "");
uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : "");
return UriComponentsBuilder.fromUriString(template)
.buildAndExpand(uriVariables)
.toUriString();
return UriComponentsBuilder.fromUriString(template).buildAndExpand(uriVariables).toUriString();
}
private static String getApplicationUri(HttpServletRequest request) {
UriComponents uriComponents = fromHttpUrl(buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath()).replaceQuery(null).fragment(null).build();
return uriComponents.toUriString();
}
private static class RegistrationIdResolver implements Converter<HttpServletRequest, String> {
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/**/{registrationId}");
@Override
@@ -131,5 +120,7 @@ public final class DefaultRelyingPartyRegistrationResolver
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
return result.getVariables().get("registrationId");
}
}
}
@@ -27,27 +27,26 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.util.Assert;
/**
* The default implementation for {@link Saml2AuthenticationRequestContextResolver}
* which uses the current request and given relying party to formulate a {@link Saml2AuthenticationRequestContext}
* The default implementation for {@link Saml2AuthenticationRequestContextResolver} which
* uses the current request and given relying party to formulate a
* {@link Saml2AuthenticationRequestContext}
*
* @author Shazin Sadakath
* @author Josh Cummings
* @since 5.4
*/
public final class DefaultSaml2AuthenticationRequestContextResolver implements Saml2AuthenticationRequestContextResolver {
public final class DefaultSaml2AuthenticationRequestContextResolver
implements Saml2AuthenticationRequestContextResolver {
private final Log logger = LogFactory.getLog(getClass());
private final Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver;
public DefaultSaml2AuthenticationRequestContextResolver
(Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
public DefaultSaml2AuthenticationRequestContextResolver(
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
}
/**
* {@inheritDoc}
*/
@Override
public Saml2AuthenticationRequestContext resolve(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
@@ -56,20 +55,19 @@ public final class DefaultSaml2AuthenticationRequestContextResolver implements S
return null;
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating SAML 2.0 Authentication Request for Asserting Party [" +
relyingParty.getRegistrationId() + "]");
this.logger.debug("Creating SAML 2.0 Authentication Request for Asserting Party ["
+ relyingParty.getRegistrationId() + "]");
}
return createRedirectAuthenticationRequestContext(request, relyingParty);
}
private Saml2AuthenticationRequestContext createRedirectAuthenticationRequestContext(
HttpServletRequest request, RelyingPartyRegistration relyingParty) {
private Saml2AuthenticationRequestContext createRedirectAuthenticationRequestContext(HttpServletRequest request,
RelyingPartyRegistration relyingParty) {
return Saml2AuthenticationRequestContext.builder()
.issuer(relyingParty.getEntityId())
return Saml2AuthenticationRequestContext.builder().issuer(relyingParty.getEntityId())
.relyingPartyRegistration(relyingParty)
.assertionConsumerServiceUrl(relyingParty.getAssertionConsumerServiceLocation())
.relayState(request.getParameter("RelayState"))
.build();
.relayState(request.getParameter("RelayState")).build();
}
}
@@ -22,7 +22,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
/**
* This {@code Saml2AuthenticationRequestContextResolver} formulates a
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf">SAML 2.0 AuthnRequest</a> (line 1968)
* <a href="https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf">SAML 2.0
* AuthnRequest</a> (line 1968)
*
* @author Shazin Sadakath
* @author Josh Cummings
@@ -31,11 +32,11 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
public interface Saml2AuthenticationRequestContextResolver {
/**
* This {@code resolve} method is defined to create a {@link Saml2AuthenticationRequestContext}
*
*
* This {@code resolve} method is defined to create a
* {@link Saml2AuthenticationRequestContext}
* @param request the current request
* @return the created {@link Saml2AuthenticationRequestContext} for the request
*/
Saml2AuthenticationRequestContext resolve(HttpServletRequest request);
}
@@ -18,8 +18,10 @@ package org.springframework.security.saml2.provider.service.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
@@ -32,36 +34,32 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.Assert;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* An {@link AuthenticationConverter} that generates a {@link Saml2AuthenticationToken} appropriate
* for authenticated a SAML 2.0 Assertion against an
* An {@link AuthenticationConverter} that generates a {@link Saml2AuthenticationToken}
* appropriate for authenticated a SAML 2.0 Assertion against an
* {@link org.springframework.security.authentication.AuthenticationManager}.
*
* @author Josh Cummings
* @since 5.4
*/
public final class Saml2AuthenticationTokenConverter implements AuthenticationConverter {
private static Base64 BASE64 = new Base64(0, new byte[]{'\n'});
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private final Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver;
/**
* Constructs a {@link Saml2AuthenticationTokenConverter} given a strategy for resolving
* Constructs a {@link Saml2AuthenticationTokenConverter} given a strategy for
* resolving {@link RelyingPartyRegistration}s
* @param relyingPartyRegistrationResolver the strategy for resolving
* {@link RelyingPartyRegistration}s
*
* @param relyingPartyRegistrationResolver the strategy for resolving {@link RelyingPartyRegistration}s
*/
public Saml2AuthenticationTokenConverter
(Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
public Saml2AuthenticationTokenConverter(
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
}
/**
* {@inheritDoc}
*/
@Override
public Saml2AuthenticationToken convert(HttpServletRequest request) {
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationResolver.convert(request);
@@ -81,9 +79,7 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo
if (HttpMethod.GET.matches(request.getMethod())) {
return samlInflate(b);
}
else {
return new String(b, UTF_8);
}
return new String(b, StandardCharsets.UTF_8);
}
private byte[] samlDecode(String s) {
@@ -93,13 +89,14 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo
private String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), UTF_8);
InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(out, new Inflater(true));
inflaterOutputStream.write(b);
inflaterOutputStream.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
catch (IOException e) {
throw new Saml2Exception("Unable to inflate string", e);
catch (IOException ex) {
throw new Saml2Exception("Unable to inflate string", ex);
}
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.provider.service.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -42,6 +43,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
public final class Saml2MetadataFilter extends OncePerRequestFilter {
private final Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationConverter;
private final Saml2MetadataResolver saml2MetadataResolver;
private RequestMatcher requestMatcher = new AntPathRequestMatcher(
@@ -58,20 +60,16 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
RequestMatcher.MatchResult matcher = this.requestMatcher.matcher(request);
if (!matcher.isMatch()) {
chain.doFilter(request, response);
return;
}
RelyingPartyRegistration relyingPartyRegistration =
this.relyingPartyRegistrationConverter.convert(request);
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationConverter.convert(request);
if (relyingPartyRegistration == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String metadata = this.saml2MetadataResolver.resolve(relyingPartyRegistration);
String registrationId = relyingPartyRegistration.getRegistrationId();
writeMetadataToResponse(response, registrationId, metadata);
@@ -79,7 +77,6 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
private void writeMetadataToResponse(HttpServletResponse response, String registrationId, String metadata)
throws IOException {
response.setContentType(MediaType.APPLICATION_XML_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"saml-" + registrationId + "-metadata.xml\"");
@@ -88,13 +85,13 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
}
/**
* Set the {@link RequestMatcher} that determines whether this filter should
* handle the incoming {@link HttpServletRequest}
*
* Set the {@link RequestMatcher} that determines whether this filter should handle
* the incoming {@link HttpServletRequest}
* @param requestMatcher
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requestMatcher = requestMatcher;
}
}
@@ -23,7 +23,7 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.springframework.security.saml2.Saml2Exception;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link OpenSamlInitializationService}
@@ -37,8 +37,9 @@ public class OpenSamlInitializationServiceTests {
OpenSamlInitializationService.initialize();
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
assertThat(registry.getParserPool()).isNotNull();
assertThatCode(() -> OpenSamlInitializationService.requireInitialize(r -> {}))
.isInstanceOf(Saml2Exception.class)
.hasMessageContaining("OpenSAML was already initialized previously");
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> OpenSamlInitializationService.requireInitialize((r) -> {
})).withMessageContaining("OpenSAML was already initialized previously");
}
}
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.core;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for verifying {@link Saml2ResponseValidatorResult}
@@ -26,8 +27,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
* @author Josh Cummings
*/
public class Saml2ResponseValidatorResultTests {
private static final Saml2Error DETAIL = new Saml2Error(
"error", "description");
private static final Saml2Error DETAIL = new Saml2Error("error", "description");
@Test
public void successWhenInvokedThenReturnsSuccessfulResult() {
@@ -64,9 +65,7 @@ public class Saml2ResponseValidatorResultTests {
@Test
public void concatResultWhenInvokedThenReturnsCopyContainingAll() {
Saml2ResponseValidatorResult failure = Saml2ResponseValidatorResult.failure(DETAIL);
Saml2ResponseValidatorResult merged = failure
.concat(failure)
.concat(failure);
Saml2ResponseValidatorResult merged = failure.concat(failure).concat(failure);
assertThat(merged.hasErrors()).isTrue();
assertThat(merged.getErrors()).containsExactly(DETAIL, DETAIL, DETAIL);
@@ -75,15 +74,22 @@ public class Saml2ResponseValidatorResultTests {
@Test
public void concatErrorWhenNullThenIllegalArgument() {
assertThatThrownBy(() -> Saml2ResponseValidatorResult.failure(DETAIL)
.concat((Saml2Error) null))
.isInstanceOf(IllegalArgumentException.class);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> Saml2ResponseValidatorResult.failure(DETAIL)
.concat((Saml2Error) null)
);
// @formatter:on
}
@Test
public void concatResultWhenNullThenIllegalArgument() {
assertThatThrownBy(() -> Saml2ResponseValidatorResult.failure(DETAIL)
.concat((Saml2ResponseValidatorResult) null))
.isInstanceOf(IllegalArgumentException.class);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> Saml2ResponseValidatorResult.failure(DETAIL)
.concat((Saml2ResponseValidatorResult) null)
);
// @formatter:on
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
@@ -27,12 +28,12 @@ import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.zip.Deflater.DEFLATED;
public final class Saml2Utils {
private static Base64 BASE64 = new Base64(0, new byte[]{'\n'});
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private Saml2Utils() {
}
public static String samlEncode(byte[] b) {
return BASE64.encodeAsString(b);
@@ -44,27 +45,29 @@ public final class Saml2Utils {
public static byte[] samlDeflate(String s) {
try {
ByteArrayOutputStream b = new ByteArrayOutputStream();
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true));
deflater.write(s.getBytes(UTF_8));
deflater.finish();
return b.toByteArray();
ByteArrayOutputStream out = new ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out,
new Deflater(Deflater.DEFLATED, true));
deflaterOutputStream.write(s.getBytes(StandardCharsets.UTF_8));
deflaterOutputStream.finish();
return out.toByteArray();
}
catch (IOException e) {
throw new Saml2Exception("Unable to deflate string", e);
catch (IOException ex) {
throw new Saml2Exception("Unable to deflate string", ex);
}
}
public static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), UTF_8);
InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(out, new Inflater(true));
inflaterOutputStream.write(b);
inflaterOutputStream.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
catch (IOException e) {
throw new Saml2Exception("Unable to inflate string", e);
catch (IOException ex) {
throw new Saml2Exception("Unable to inflate string", ex);
}
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.core;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -27,12 +28,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.security.converter.RsaKeyConverters;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.SIGNING;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
public class Saml2X509CredentialTests {
@@ -40,159 +36,161 @@ public class Saml2X509CredentialTests {
public ExpectedException exception = ExpectedException.none();
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)));
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-----";
this.key = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(keyData.getBytes(StandardCharsets.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)));
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-----";
this.certificate = (X509Certificate) factory
.generateCertificate(new ByteArrayInputStream(certificateData.getBytes(StandardCharsets.UTF_8)));
}
@Test
public void constructorWhenRelyingPartyWithCredentialsThenItSucceeds() {
new Saml2X509Credential(key, certificate, SIGNING);
new Saml2X509Credential(key, certificate, SIGNING, DECRYPTION);
new Saml2X509Credential(key, certificate, DECRYPTION);
Saml2X509Credential.signing(key, certificate);
Saml2X509Credential.decryption(key, certificate);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.SIGNING);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.SIGNING,
Saml2X509CredentialType.DECRYPTION);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.DECRYPTION);
Saml2X509Credential.signing(this.key, this.certificate);
Saml2X509Credential.decryption(this.key, this.certificate);
}
@Test
public void constructorWhenAssertingPartyWithCredentialsThenItSucceeds() {
new Saml2X509Credential(certificate, VERIFICATION);
new Saml2X509Credential(certificate, VERIFICATION, ENCRYPTION);
new Saml2X509Credential(certificate, ENCRYPTION);
Saml2X509Credential.verification(certificate);
Saml2X509Credential.encryption(certificate);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.VERIFICATION);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.VERIFICATION,
Saml2X509CredentialType.ENCRYPTION);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.ENCRYPTION);
Saml2X509Credential.verification(this.certificate);
Saml2X509Credential.encryption(this.certificate);
}
@Test
public void constructorWhenRelyingPartyWithoutCredentialsThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, (X509Certificate) null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, (X509Certificate) null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithoutPrivateKeyThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, certificate, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, this.certificate, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(key, null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(this.key, null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenAssertingPartyWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithEncryptionUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(key, certificate, ENCRYPTION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.ENCRYPTION);
}
@Test
public void constructorWhenRelyingPartyWithVerificationUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(key, certificate, VERIFICATION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.VERIFICATION);
}
@Test
public void constructorWhenAssertingPartyWithSigningUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(certificate, SIGNING);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenAssertingPartyWithDecryptionUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(certificate, DECRYPTION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.DECRYPTION);
}
@Test
public void factoryWhenRelyingPartyForSigningWithoutCredentialsThenItFails() {
exception.expect(IllegalArgumentException.class);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.signing(null, null);
}
@Test
public void factoryWhenRelyingPartyForSigningWithoutPrivateKeyThenItFails() {
exception.expect(IllegalArgumentException.class);
Saml2X509Credential.signing(null, certificate);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.signing(null, this.certificate);
}
@Test
public void factoryWhenRelyingPartyForSigningWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
Saml2X509Credential.signing(key, null);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.signing(this.key, null);
}
@Test
public void factoryWhenRelyingPartyForDecryptionWithoutCredentialsThenItFails() {
exception.expect(IllegalArgumentException.class);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.decryption(null, null);
}
@Test
public void factoryWhenRelyingPartyForDecryptionWithoutPrivateKeyThenItFails() {
exception.expect(IllegalArgumentException.class);
Saml2X509Credential.decryption(null, certificate);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.decryption(null, this.certificate);
}
@Test
public void factoryWhenRelyingPartyForDecryptionWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
Saml2X509Credential.decryption(key, null);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.decryption(this.key, null);
}
@Test
public void factoryWhenAssertingPartyForVerificationWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.verification(null);
}
@Test
public void factoryWhenAssertingPartyForEncryptionWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
this.exception.expect(IllegalArgumentException.class);
Saml2X509Credential.encryption(null);
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.core;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
@@ -26,154 +27,147 @@ import java.security.cert.X509Certificate;
import org.opensaml.security.crypto.KeySupport;
import org.springframework.security.saml2.Saml2Exception;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.SIGNING;
import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
public final class TestSaml2X509Credentials {
private TestSaml2X509Credentials() {
}
public static Saml2X509Credential assertingPartySigningCredential() {
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING);
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING);
}
public static Saml2X509Credential assertingPartyEncryptingCredential() {
return new Saml2X509Credential(spCertificate(), ENCRYPTION);
return new Saml2X509Credential(spCertificate(), Saml2X509CredentialType.ENCRYPTION);
}
public static Saml2X509Credential assertingPartyPrivateCredential() {
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING, DECRYPTION);
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING,
Saml2X509CredentialType.DECRYPTION);
}
public static Saml2X509Credential relyingPartyVerifyingCredential() {
return new Saml2X509Credential(idpCertificate(), VERIFICATION);
return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.VERIFICATION);
}
public static Saml2X509Credential relyingPartySigningCredential() {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), SIGNING);
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.SIGNING);
}
public static Saml2X509Credential relyingPartyDecryptingCredential() {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), DECRYPTION);
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.DECRYPTION);
}
private static X509Certificate certificate(String cert) {
ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes());
try {
return (X509Certificate) CertificateFactory
.getInstance("X.509")
.generateCertificate(certBytes);
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certBytes);
}
catch (CertificateException e) {
throw new Saml2Exception(e);
catch (CertificateException ex) {
throw new Saml2Exception(ex);
}
}
private static PrivateKey privateKey(String key) {
try {
return KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]);
return KeySupport.decodePrivateKey(key.getBytes(StandardCharsets.UTF_8), new char[0]);
}
catch (KeyException e) {
throw new Saml2Exception(e);
catch (KeyException ex) {
throw new Saml2Exception(ex);
}
}
private static X509Certificate idpCertificate() {
return certificate("-----BEGIN CERTIFICATE-----\n"
+ "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n"
+ "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n"
+ "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n"
+ "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n"
+ "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n"
+ "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n"
+ "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n"
+ "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n"
+ "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n"
+ "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n"
+ "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n"
+ "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n"
+ "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n"
+ "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n"
+ "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n"
+ "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n"
+ "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n"
+ "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n"
+ "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n"
+ "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n"
+ "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n"
+ "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n"
+ "-----END CERTIFICATE-----\n");
private static X509Certificate idpCertificate() {
return certificate(
"-----BEGIN CERTIFICATE-----\n" + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n"
+ "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n"
+ "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n"
+ "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n"
+ "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n"
+ "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n"
+ "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n"
+ "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n"
+ "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n"
+ "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n"
+ "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n"
+ "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n"
+ "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n"
+ "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n"
+ "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n"
+ "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n"
+ "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n"
+ "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n"
+ "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n"
+ "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n"
+ "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n"
+ "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + "-----END CERTIFICATE-----\n");
}
private static PrivateKey idpPrivateKey() {
return privateKey("-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n"
+ "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n"
+ "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n"
+ "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n"
+ "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n"
+ "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n"
+ "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n"
+ "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n"
+ "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n"
+ "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n"
+ "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n"
+ "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n"
+ "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n"
+ "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n"
+ "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n"
+ "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n"
+ "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n"
+ "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n"
+ "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n"
+ "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n"
+ "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n"
+ "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n"
+ "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n"
+ "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n"
+ "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n" + "xk6Mox+u8Cc2eAK12H13i+8=\n"
+ "-----END PRIVATE KEY-----\n");
return privateKey(
"-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n"
+ "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n"
+ "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n"
+ "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n"
+ "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n"
+ "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n"
+ "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n"
+ "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n"
+ "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n"
+ "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n"
+ "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n"
+ "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n"
+ "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n"
+ "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n"
+ "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n"
+ "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n"
+ "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n"
+ "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n"
+ "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n"
+ "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n"
+ "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n"
+ "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n"
+ "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n"
+ "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n"
+ "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n"
+ "xk6Mox+u8Cc2eAK12H13i+8=\n" + "-----END PRIVATE KEY-----\n");
}
private static X509Certificate spCertificate() {
return certificate("-----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-----");
return certificate(
"-----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-----");
}
private static PrivateKey spPrivateKey() {
return privateKey("-----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-----");
return privateKey(
"-----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-----");
}
}
@@ -16,23 +16,19 @@
package org.springframework.security.saml2.credentials;
import org.springframework.security.converter.RsaKeyConverters;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
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;
import org.springframework.security.converter.RsaKeyConverters;
import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType;
public class Saml2X509CredentialTests {
@@ -40,110 +36,111 @@ public class Saml2X509CredentialTests {
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)));
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-----";
this.key = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(keyData.getBytes(StandardCharsets.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)));
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-----";
this.certificate = (X509Certificate) factory
.generateCertificate(new ByteArrayInputStream(certificateData.getBytes(StandardCharsets.UTF_8)));
}
@Test
public void constructorWhenRelyingPartyWithCredentialsThenItSucceeds() {
new Saml2X509Credential(key, certificate, SIGNING);
new Saml2X509Credential(key, certificate, SIGNING, DECRYPTION);
new Saml2X509Credential(key, certificate, DECRYPTION);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.SIGNING);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.SIGNING,
Saml2X509CredentialType.DECRYPTION);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.DECRYPTION);
}
@Test
public void constructorWhenAssertingPartyWithCredentialsThenItSucceeds() {
new Saml2X509Credential(certificate, VERIFICATION);
new Saml2X509Credential(certificate, VERIFICATION, ENCRYPTION);
new Saml2X509Credential(certificate, ENCRYPTION);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.VERIFICATION);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.VERIFICATION,
Saml2X509CredentialType.ENCRYPTION);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.ENCRYPTION);
}
@Test
public void constructorWhenRelyingPartyWithoutCredentialsThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, (X509Certificate) null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, (X509Certificate) null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithoutPrivateKeyThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, certificate, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, this.certificate, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(key, null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(this.key, null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenAssertingPartyWithoutCertificateThenItFails() {
exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, SIGNING);
this.exception.expect(IllegalArgumentException.class);
new Saml2X509Credential(null, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenRelyingPartyWithEncryptionUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(key, certificate, ENCRYPTION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.ENCRYPTION);
}
@Test
public void constructorWhenRelyingPartyWithVerificationUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(key, certificate, VERIFICATION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.key, this.certificate, Saml2X509CredentialType.VERIFICATION);
}
@Test
public void constructorWhenAssertingPartyWithSigningUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(certificate, SIGNING);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.SIGNING);
}
@Test
public void constructorWhenAssertingPartyWithDecryptionUsageThenItFails() {
exception.expect(IllegalStateException.class);
new Saml2X509Credential(certificate, DECRYPTION);
this.exception.expect(IllegalStateException.class);
new Saml2X509Credential(this.certificate, Saml2X509CredentialType.DECRYPTION);
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.credentials;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
@@ -26,154 +27,147 @@ import java.security.cert.X509Certificate;
import org.opensaml.security.crypto.KeySupport;
import org.springframework.security.saml2.Saml2Exception;
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;
import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType;
public final class TestSaml2X509Credentials {
private TestSaml2X509Credentials() {
}
public static Saml2X509Credential assertingPartySigningCredential() {
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING);
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING);
}
public static Saml2X509Credential assertingPartyEncryptingCredential() {
return new Saml2X509Credential(spCertificate(), ENCRYPTION);
return new Saml2X509Credential(spCertificate(), Saml2X509CredentialType.ENCRYPTION);
}
public static Saml2X509Credential assertingPartyPrivateCredential() {
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING, DECRYPTION);
return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING,
Saml2X509CredentialType.DECRYPTION);
}
public static Saml2X509Credential relyingPartyVerifyingCredential() {
return new Saml2X509Credential(idpCertificate(), VERIFICATION);
return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.VERIFICATION);
}
public static Saml2X509Credential relyingPartySigningCredential() {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), SIGNING);
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.SIGNING);
}
public static Saml2X509Credential relyingPartyDecryptingCredential() {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), DECRYPTION);
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.DECRYPTION);
}
private static X509Certificate certificate(String cert) {
ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes());
try {
return (X509Certificate) CertificateFactory
.getInstance("X.509")
.generateCertificate(certBytes);
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certBytes);
}
catch (CertificateException e) {
throw new Saml2Exception(e);
catch (CertificateException ex) {
throw new Saml2Exception(ex);
}
}
private static PrivateKey privateKey(String key) {
try {
return KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]);
return KeySupport.decodePrivateKey(key.getBytes(StandardCharsets.UTF_8), new char[0]);
}
catch (KeyException e) {
throw new Saml2Exception(e);
catch (KeyException ex) {
throw new Saml2Exception(ex);
}
}
private static X509Certificate idpCertificate() {
return certificate("-----BEGIN CERTIFICATE-----\n"
+ "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n"
+ "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n"
+ "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n"
+ "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n"
+ "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n"
+ "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n"
+ "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n"
+ "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n"
+ "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n"
+ "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n"
+ "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n"
+ "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n"
+ "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n"
+ "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n"
+ "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n"
+ "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n"
+ "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n"
+ "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n"
+ "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n"
+ "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n"
+ "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n"
+ "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n"
+ "-----END CERTIFICATE-----\n");
private static X509Certificate idpCertificate() {
return certificate(
"-----BEGIN CERTIFICATE-----\n" + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n"
+ "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n"
+ "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n"
+ "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n"
+ "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n"
+ "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n"
+ "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n"
+ "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n"
+ "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n"
+ "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n"
+ "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n"
+ "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n"
+ "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n"
+ "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n"
+ "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n"
+ "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n"
+ "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n"
+ "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n"
+ "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n"
+ "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n"
+ "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n"
+ "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + "-----END CERTIFICATE-----\n");
}
private static PrivateKey idpPrivateKey() {
return privateKey("-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n"
+ "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n"
+ "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n"
+ "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n"
+ "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n"
+ "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n"
+ "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n"
+ "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n"
+ "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n"
+ "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n"
+ "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n"
+ "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n"
+ "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n"
+ "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n"
+ "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n"
+ "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n"
+ "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n"
+ "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n"
+ "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n"
+ "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n"
+ "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n"
+ "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n"
+ "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n"
+ "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n"
+ "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n" + "xk6Mox+u8Cc2eAK12H13i+8=\n"
+ "-----END PRIVATE KEY-----\n");
return privateKey(
"-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n"
+ "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n"
+ "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n"
+ "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n"
+ "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n"
+ "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n"
+ "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n"
+ "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n"
+ "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n"
+ "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n"
+ "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n"
+ "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n"
+ "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n"
+ "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n"
+ "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n"
+ "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n"
+ "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n"
+ "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n"
+ "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n"
+ "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n"
+ "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n"
+ "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n"
+ "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n"
+ "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n"
+ "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n"
+ "xk6Mox+u8Cc2eAK12H13i+8=\n" + "-----END PRIVATE KEY-----\n");
}
private static X509Certificate spCertificate() {
return certificate("-----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-----");
return certificate(
"-----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-----");
}
private static PrivateKey spPrivateKey() {
return privateKey("-----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-----");
return privateKey(
"-----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-----");
}
}
@@ -16,17 +16,17 @@
package org.springframework.security.saml2.provider.service.authentication;
import org.joda.time.DateTime;
import org.junit.Test;
import java.time.Instant;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
public class DefaultSaml2AuthenticatedPrincipalTests {
@@ -43,16 +43,14 @@ public class DefaultSaml2AuthenticatedPrincipalTests {
public void createDefaultSaml2AuthenticatedPrincipalWhenNameNullThenException() {
Map<String, List<Object>> attributes = new LinkedHashMap<>();
attributes.put("email", Arrays.asList("john.doe@example.com", "doe.john@example.com"));
assertThatCode(() -> new DefaultSaml2AuthenticatedPrincipal(null, attributes))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("name cannot be null");
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal(null, attributes))
.withMessageContaining("name cannot be null");
}
@Test
public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() {
assertThatCode(() -> new DefaultSaml2AuthenticatedPrincipal("user", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("attributes cannot be null");
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", null))
.withMessageContaining("attributes cannot be null");
}
@Test
@@ -75,16 +73,13 @@ public class DefaultSaml2AuthenticatedPrincipalTests {
public void getAttributeWhenDistinctValuesThenReturnsValues() {
final Boolean registered = true;
final Instant registeredDate = Instant.ofEpochMilli(DateTime.parse("1970-01-01T00:00:00Z").getMillis());
Map<String, List<Object>> attributes = new LinkedHashMap<>();
attributes.put("registration", Arrays.asList(registered, registeredDate));
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", attributes);
List<Object> registrationInfo = principal.getAttribute("registration");
assertThat(registrationInfo).isNotNull();
assertThat((Boolean) registrationInfo.get(0)).isEqualTo(registered);
assertThat((Instant) registrationInfo.get(1)).isEqualTo(registeredDate);
}
}
@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
@@ -38,9 +39,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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.saml.common.assertion.ValidationContext;
import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.EncryptedAssertion;
@@ -56,36 +59,17 @@ import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.util.StringUtils;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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;
import static org.mockito.Mockito.when;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getMarshallerFactory;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_ASSERTION;
import static org.springframework.security.saml2.core.Saml2ErrorCodes.INVALID_SIGNATURE;
import static org.springframework.security.saml2.core.Saml2ResponseValidatorResult.success;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.assertingPartyEncryptingCredential;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.assertingPartyPrivateCredential;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.assertingPartySigningCredential;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyDecryptingCredential;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider.createDefaultAssertionValidator;
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider.createDefaultResponseAuthenticationConverter;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.assertion;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.attributeStatements;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.encrypted;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.response;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.signed;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.signedResponseWithOneAssertion;
import static org.springframework.util.StringUtils.hasText;
/**
* Tests for {@link OpenSamlAuthenticationProvider}
@@ -96,23 +80,27 @@ import static org.springframework.util.StringUtils.hasText;
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());
private Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("name",
Collections.emptyMap());
private Saml2Authentication authentication = new Saml2Authentication(this.principal, "response",
Collections.emptyList());
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() {
assertThat(this.provider.supports(Saml2AuthenticationToken.class))
.withFailMessage(OpenSamlAuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class)
.withFailMessage(
OpenSamlAuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class)
.isTrue();
}
@@ -126,123 +114,114 @@ public class OpenSamlAuthenticationProviderTests {
@Test
public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA));
Assertion assertion = (Assertion) getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME)
.buildObject(Assertion.DEFAULT_ELEMENT_NAME);
this.provider.authenticate(token(serialize(assertion), relyingPartyVerifyingCredential()));
Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory()
.getBuilder(Assertion.DEFAULT_ELEMENT_NAME).buildObject(Assertion.DEFAULT_ELEMENT_NAME);
this.provider
.authenticate(token(serialize(assertion), TestSaml2X509Credentials.relyingPartyVerifyingCredential()));
}
@Test
public void authenticateWhenXmlErrorThenThrowAuthenticationException() {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA));
Saml2AuthenticationToken token = token("invalid xml", relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token("invalid xml",
TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_DESTINATION));
Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
response.getAssertions().add(assertion());
signed(response, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Response response = TestOpenSamlObjects.response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
response.getAssertions().add(TestOpenSamlObjects.assertion());
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() {
this.exception.expect(
authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")
);
Saml2AuthenticationToken token = token(response(), assertingPartySigningCredential());
authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response."));
Saml2AuthenticationToken token = token(TestOpenSamlObjects.response(),
TestSaml2X509Credentials.assertingPartySigningCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_SIGNATURE));
Response response = response();
response.getAssertions().add(assertion());
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Response response = TestOpenSamlObjects.response();
response.getAssertions().add(TestOpenSamlObjects.assertion());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() throws Exception {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_ASSERTION));
Response response = response();
Assertion assertion = assertion();
assertion
.getSubject()
.getSubjectConfirmations()
.get(0)
.getSubjectConfirmationData()
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData()
.setNotOnOrAfter(DateTime.now().minus(Duration.standardDays(3)));
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
response.getAssertions().add(assertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.SUBJECT_NOT_FOUND));
Response response = response();
Assertion assertion = assertion();
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
assertion.setSubject(null);
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
response.getAssertions().add(assertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenUsernameMissingThenThrowAuthenticationException() throws Exception {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.SUBJECT_NOT_FOUND));
Response response = response();
Assertion assertion = assertion();
assertion
.getSubject()
.getNameID()
.setValue(null);
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
assertion.getSubject().getNameID().setValue(null);
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
response.getAssertions().add(assertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() throws Exception {
Response response = response();
Assertion assertion = assertion();
assertion.getSubject().getSubjectConfirmations().forEach(
sc -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")
);
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.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, relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenAssertionContainsAttributesThenItSucceeds() {
Response response = response();
Assertion assertion = assertion();
List<AttributeStatement> attributes = attributeStatements();
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
List<AttributeStatement> attributes = TestOpenSamlObjects.attributeStatements();
assertion.getAttributeStatements().addAll(attributes);
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
response.getAssertions().add(assertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
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"));
@@ -251,7 +230,6 @@ public class OpenSamlAuthenticationProviderTests {
expected.put("registered", Collections.singletonList(true));
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);
}
@@ -259,84 +237,94 @@ public class OpenSamlAuthenticationProviderTests {
@Test
public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() throws Exception {
this.exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_SIGNATURE));
Response response = response();
EncryptedAssertion encryptedAssertion = encrypted(assertion(), assertingPartyEncryptingCredential());
Response response = TestOpenSamlObjects.response();
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(response, relyingPartyDecryptingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyDecryptingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() throws Exception {
Response response = response();
Assertion assertion = signed(assertion(), assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
EncryptedAssertion encryptedAssertion = encrypted(assertion, assertingPartyEncryptingCredential());
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential(), relyingPartyDecryptingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential(),
TestSaml2X509Credentials.relyingPartyDecryptingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() throws Exception {
Response response = response();
EncryptedAssertion encryptedAssertion = encrypted(assertion(), assertingPartyEncryptingCredential());
Response response = TestOpenSamlObjects.response();
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
signed(response, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential(), relyingPartyDecryptingCredential());
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential(),
TestSaml2X509Credentials.relyingPartyDecryptingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() throws Exception {
Response response = response();
Assertion assertion = assertion();
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
NameID nameId = assertion.getSubject().getNameID();
EncryptedID encryptedID = encrypted(nameId, assertingPartyEncryptingCredential());
EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId,
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
assertion.getSubject().setNameID(null);
assertion.getSubject().setEncryptedID(encryptedID);
response.getAssertions().add(assertion);
signed(assertion, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential(), relyingPartyDecryptingCredential());
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
RELYING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential(),
TestSaml2X509Credentials.relyingPartyDecryptingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() throws Exception {
this.exception.expect(
authenticationMatcher(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")
);
Response response = response();
EncryptedAssertion encryptedAssertion = encrypted(assertion(), assertingPartyEncryptingCredential());
this.exception
.expect(authenticationMatcher(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData"));
Response response = TestOpenSamlObjects.response();
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(serialize(response), relyingPartyVerifyingCredential());
Saml2AuthenticationToken token = token(serialize(response),
TestSaml2X509Credentials.relyingPartyVerifyingCredential());
this.provider.authenticate(token);
}
@Test
public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() throws Exception {
this.exception.expect(
authenticationMatcher(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")
);
Response response = response();
EncryptedAssertion encryptedAssertion = encrypted(assertion(), assertingPartyEncryptingCredential());
this.exception
.expect(authenticationMatcher(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData"));
Response response = TestOpenSamlObjects.response();
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(serialize(response), assertingPartyPrivateCredential());
Saml2AuthenticationToken token = token(serialize(response),
TestSaml2X509Credentials.assertingPartyPrivateCredential());
this.provider.authenticate(token);
}
@Test
public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException {
Response response = response();
Assertion assertion = signed(assertion(), assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
EncryptedAssertion encryptedAssertion = encrypted(assertion, assertingPartyEncryptingCredential());
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential(), relyingPartyDecryptingCredential());
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential(),
TestSaml2X509Credentials.relyingPartyDecryptingCredential());
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);
@@ -346,48 +334,58 @@ public class OpenSamlAuthenticationProviderTests {
@Test
public void createDefaultAssertionValidatorWhenAssertionThenValidates() {
Response response = signedResponseWithOneAssertion();
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
Assertion assertion = response.getAssertions().get(0);
OpenSamlAuthenticationProvider.AssertionToken assertionToken =
new OpenSamlAuthenticationProvider.AssertionToken(assertion, token());
assertThat(
createDefaultAssertionValidator().convert(assertionToken)
.hasErrors()).isFalse();
OpenSamlAuthenticationProvider.AssertionToken assertionToken = new OpenSamlAuthenticationProvider.AssertionToken(
assertion, token());
assertThat(OpenSamlAuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors())
.isFalse();
}
@Test
public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() {
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setAssertionValidator(assertionToken ->
createDefaultAssertionValidator(token -> new ValidationContext()).convert(assertionToken)
.concat(new Saml2Error("wrong error", "wrong error")));
Response response = response();
Assertion assertion = assertion();
// @formatter:off
provider.setAssertionValidator((assertionToken) -> OpenSamlAuthenticationProvider
.createDefaultAssertionValidator((token) -> new ValidationContext())
.convert(assertionToken)
.concat(new Saml2Error("wrong error", "wrong error"))
);
// @formatter:on
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME);
assertion.getConditions().getConditions().add(oneTimeUse);
response.getAssertions().add(assertion);
signed(response, assertingPartySigningCredential(), ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
assertThatThrownBy(() -> provider.authenticate(token))
.isInstanceOf(Saml2AuthenticationException.class)
.hasFieldOrPropertyWithValue("error.errorCode", INVALID_ASSERTION);
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
// @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);
Converter<OpenSamlAuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> validator = mock(
Converter.class);
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setAssertionValidator(assertionToken ->
createDefaultAssertionValidator().convert(assertionToken)
.concat(validator.convert(assertionToken)));
Response response = response();
Assertion assertion = assertion();
// @formatter:off
provider.setAssertionValidator((assertionToken) -> OpenSamlAuthenticationProvider.createDefaultAssertionValidator()
.convert(assertionToken)
.concat(validator.convert(assertionToken))
);
// @formatter:on
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
response.getAssertions().add(assertion);
signed(response, assertingPartySigningCredential(), ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
when(validator.convert(any(OpenSamlAuthenticationProvider.AssertionToken.class)))
.thenReturn(success());
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
given(validator.convert(any(OpenSamlAuthenticationProvider.AssertionToken.class)))
.willReturn(Saml2ResponseValidatorResult.success());
provider.authenticate(token);
verify(validator).convert(any(OpenSamlAuthenticationProvider.AssertionToken.class));
}
@@ -395,83 +393,97 @@ public class OpenSamlAuthenticationProviderTests {
@Test
public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() {
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setAssertionValidator(assertionToken -> success());
Response response = response();
Assertion assertion = assertion();
signed(assertion, relyingPartyDecryptingCredential(), RELYING_PARTY_ENTITY_ID); // broken signature
provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success());
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
RELYING_PARTY_ENTITY_ID); // broken
// signature
response.getAssertions().add(assertion);
signed(response, assertingPartySigningCredential(), ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
assertThatThrownBy(() -> provider.authenticate(token))
.isInstanceOf(Saml2AuthenticationException.class)
.hasFieldOrPropertyWithValue("error.errorCode", INVALID_SIGNATURE);
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
// @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(SC_VALID_RECIPIENTS, singleton("blah"));
parameters.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton("blah"));
ValidationContext context = mock(ValidationContext.class);
when(context.getStaticParameters()).thenReturn(parameters);
given(context.getStaticParameters()).willReturn(parameters);
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setAssertionValidator(createDefaultAssertionValidator(assertionToken -> context));
Response response = response();
Assertion assertion = assertion();
provider.setAssertionValidator(
OpenSamlAuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context));
Response response = TestOpenSamlObjects.response();
Assertion assertion = TestOpenSamlObjects.assertion();
response.getAssertions().add(assertion);
signed(response, assertingPartySigningCredential(), ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
assertThatThrownBy(() -> provider.authenticate(token))
.isInstanceOf(Saml2AuthenticationException.class)
.hasMessageContaining("Invalid assertion");
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
ASSERTING_PARTY_ENTITY_ID);
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
// @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 setAssertionValidatorWhenNullThenIllegalArgument() {
assertThatCode(() -> this.provider.setAssertionValidator(null))
.isInstanceOf(IllegalArgumentException.class);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.provider.setAssertionValidator(null));
// @formatter:on
}
@Test
public void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() {
Response response = signedResponseWithOneAssertion();
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
OpenSamlAuthenticationProvider.ResponseToken responseToken =
new OpenSamlAuthenticationProvider.ResponseToken(response, token);
Saml2Authentication authentication = createDefaultResponseAuthenticationConverter()
.convert(responseToken);
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
OpenSamlAuthenticationProvider.ResponseToken responseToken = new OpenSamlAuthenticationProvider.ResponseToken(
response, token);
Saml2Authentication authentication = OpenSamlAuthenticationProvider
.createDefaultResponseAuthenticationConverter().convert(responseToken);
assertThat(authentication.getName()).isEqualTo("test@saml.user");
}
@Test
public void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() {
Converter<OpenSamlAuthenticationProvider.ResponseToken, Saml2Authentication> authenticationConverter =
mock(Converter.class);
Converter<OpenSamlAuthenticationProvider.ResponseToken, Saml2Authentication> authenticationConverter = mock(
Converter.class);
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setResponseAuthenticationConverter(authenticationConverter);
Response response = signedResponseWithOneAssertion();
Saml2AuthenticationToken token = token(response, relyingPartyVerifyingCredential());
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
Saml2AuthenticationToken token = token(response, TestSaml2X509Credentials.relyingPartyVerifyingCredential());
provider.authenticate(token);
verify(authenticationConverter).convert(any());
}
@Test
public void setResponseAuthenticationConverterWhenNullThenIllegalArgument() {
assertThatCode(() -> this.provider.setResponseAuthenticationConverter(null))
.isInstanceOf(IllegalArgumentException.class);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null));
// @formatter:on
}
private <T extends XMLObject> T build(QName qName) {
return (T) getBuilderFactory().getBuilder(qName).buildObject(qName);
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}
private String serialize(XMLObject object) {
try {
Marshaller marshaller = getMarshallerFactory().getMarshaller(object);
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object);
Element element = marshaller.marshall(object);
return SerializeSupport.nodeToString(element);
} catch (MarshallingException e) {
throw new Saml2Exception(e);
}
catch (MarshallingException ex) {
throw new Saml2Exception(ex);
}
}
@@ -490,7 +502,7 @@ public class OpenSamlAuthenticationProviderTests {
if (!code.equals(ex.getError().getErrorCode())) {
return false;
}
if (hasText(description)) {
if (StringUtils.hasText(description)) {
if (!description.equals(ex.getError().getDescription())) {
return false;
}
@@ -500,15 +512,14 @@ public class OpenSamlAuthenticationProviderTests {
@Override
public void describeTo(Description desc) {
String excepting = "Saml2AuthenticationException[code="+code+"; description="+description+"]";
String excepting = "Saml2AuthenticationException[code=" + code + "; description=" + description + "]";
desc.appendText(excepting);
}
};
}
private Saml2AuthenticationToken token() {
return token(response(), relyingPartyVerifyingCredential());
return token(TestOpenSamlObjects.response(), TestSaml2X509Credentials.relyingPartyVerifyingCredential());
}
private Saml2AuthenticationToken token(Response response, Saml2X509Credential... credentials) {
@@ -517,7 +528,8 @@ public class OpenSamlAuthenticationProviderTests {
}
private Saml2AuthenticationToken token(String payload, Saml2X509Credential... credentials) {
return new Saml2AuthenticationToken(payload,
DESTINATION, ASSERTING_PARTY_ENTITY_ID, RELYING_PARTY_ENTITY_ID, Arrays.asList(credentials));
return new Saml2AuthenticationToken(payload, DESTINATION, ASSERTING_PARTY_ENTITY_ID, RELYING_PARTY_ENTITY_ID,
Arrays.asList(credentials));
}
}
@@ -17,12 +17,14 @@
package org.springframework.security.saml2.provider.service.authentication;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller;
@@ -31,25 +33,16 @@ import org.w3c.dom.Element;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getParserPool;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getUnmarshallerFactory;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartySigningCredential;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlInflate;
import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.authnRequest;
import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRelyingPartyRegistration;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
/**
* Tests for {@link OpenSamlAuthenticationRequestFactory}
@@ -57,10 +50,13 @@ import static org.springframework.security.saml2.provider.service.registration.S
public class OpenSamlAuthenticationRequestFactoryTests {
private OpenSamlAuthenticationRequestFactory factory;
private Saml2AuthenticationRequestContext.Builder contextBuilder;
private Saml2AuthenticationRequestContext context;
private RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder;
private RelyingPartyRegistration relyingPartyRegistration;
private AuthnRequestUnmarshaller unmarshaller;
@@ -72,120 +68,109 @@ public class OpenSamlAuthenticationRequestFactoryTests {
public void setUp() {
this.relyingPartyRegistrationBuilder = RelyingPartyRegistration.withRegistrationId("id")
.assertionConsumerServiceLocation("template")
.providerDetails(c -> c.webSsoUrl("https://destination/sso"))
.providerDetails(c -> c.entityId("remote-entity-id"))
.localEntityIdTemplate("local-entity-id")
.credentials(c -> c.add(relyingPartySigningCredential()));
.providerDetails((c) -> c.webSsoUrl("https://destination/sso"))
.providerDetails((c) -> c.entityId("remote-entity-id")).localEntityIdTemplate("local-entity-id")
.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential()));
this.relyingPartyRegistration = this.relyingPartyRegistrationBuilder.build();
contextBuilder = Saml2AuthenticationRequestContext.builder()
.issuer("https://issuer")
.relyingPartyRegistration(relyingPartyRegistration)
this.contextBuilder = Saml2AuthenticationRequestContext.builder().issuer("https://issuer")
.relyingPartyRegistration(this.relyingPartyRegistration)
.assertionConsumerServiceUrl("https://issuer/sso");
context = contextBuilder.build();
factory = new OpenSamlAuthenticationRequestFactory();
this.unmarshaller =(AuthnRequestUnmarshaller) getUnmarshallerFactory()
this.context = this.contextBuilder.build();
this.factory = new OpenSamlAuthenticationRequestFactory();
this.unmarshaller = (AuthnRequestUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory()
.getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME);
}
@Test
public void createAuthenticationRequestWhenInvokingDeprecatedMethodThenReturnsXML() {
Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(context).build();
String result = factory.createAuthenticationRequest(request);
assertThat(result.replace("\n", "")).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2p:AuthnRequest");
Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(this.context)
.build();
String result = this.factory.createAuthenticationRequest(request);
assertThat(result.replace("\n", ""))
.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2p:AuthnRequest");
}
@Test
public void createRedirectAuthenticationRequestWhenUsingContextThenAllValuesAreSet() {
context = contextBuilder
.relayState("Relay State Value")
.build();
Saml2RedirectAuthenticationRequest result = factory.createRedirectAuthenticationRequest(context);
this.context = this.contextBuilder.relayState("Relay State Value").build();
Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(this.context);
assertThat(result.getSamlRequest()).isNotEmpty();
assertThat(result.getRelayState()).isEqualTo("Relay State Value");
assertThat(result.getSigAlg()).isNotEmpty();
assertThat(result.getSignature()).isNotEmpty();
assertThat(result.getBinding()).isEqualTo(REDIRECT);
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
}
@Test
public void createRedirectAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
context = contextBuilder
.relayState("Relay State Value")
this.context = this.contextBuilder.relayState("Relay State Value")
.relyingPartyRegistration(
withRelyingPartyRegistration(relyingPartyRegistration)
.providerDetails(c -> c.signAuthNRequest(false))
.build()
)
RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration)
.providerDetails((c) -> c.signAuthNRequest(false)).build())
.build();
Saml2RedirectAuthenticationRequest result = factory.createRedirectAuthenticationRequest(context);
Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(this.context);
assertThat(result.getSamlRequest()).isNotEmpty();
assertThat(result.getRelayState()).isEqualTo("Relay State Value");
assertThat(result.getSigAlg()).isNull();
assertThat(result.getSignature()).isNull();
assertThat(result.getBinding()).isEqualTo(REDIRECT);
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
}
@Test
public void createPostAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
context = contextBuilder
.relayState("Relay State Value")
this.context = this.contextBuilder.relayState("Relay State Value")
.relyingPartyRegistration(
withRelyingPartyRegistration(relyingPartyRegistration)
.providerDetails(c -> c.signAuthNRequest(false))
.build()
)
RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration)
.providerDetails((c) -> c.signAuthNRequest(false)).build())
.build();
Saml2PostAuthenticationRequest result = factory.createPostAuthenticationRequest(context);
Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context);
assertThat(result.getSamlRequest()).isNotEmpty();
assertThat(result.getRelayState()).isEqualTo("Relay State Value");
assertThat(result.getBinding()).isEqualTo(POST);
assertThat(new String(samlDecode(result.getSamlRequest()), UTF_8))
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8))
.doesNotContain("ds:Signature");
}
@Test
public void createPostAuthenticationRequestWhenSignRequestThenSignatureIsPresent() {
context = contextBuilder
.relayState("Relay State Value")
this.context = this.contextBuilder.relayState("Relay State Value")
.relyingPartyRegistration(
withRelyingPartyRegistration(relyingPartyRegistration)
.build()
)
RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration).build())
.build();
Saml2PostAuthenticationRequest result = factory.createPostAuthenticationRequest(context);
Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context);
assertThat(result.getSamlRequest()).isNotEmpty();
assertThat(result.getRelayState()).isEqualTo("Relay State Value");
assertThat(result.getBinding()).isEqualTo(POST);
assertThat(new String(samlDecode(result.getSamlRequest()), UTF_8))
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8))
.contains("ds:Signature");
}
@Test
public void createAuthenticationRequestWhenDefaultThenReturnsPostBinding() {
AuthnRequest authn = getAuthNRequest(POST);
AuthnRequest authn = getAuthNRequest(Saml2MessageBinding.POST);
Assert.assertEquals(SAMLConstants.SAML2_POST_BINDING_URI, authn.getProtocolBinding());
}
@Test
public void createAuthenticationRequestWhenSetUriThenReturnsCorrectBinding() {
factory.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
AuthnRequest authn = getAuthNRequest(POST);
this.factory.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
AuthnRequest authn = getAuthNRequest(Saml2MessageBinding.POST);
Assert.assertEquals(SAMLConstants.SAML2_REDIRECT_BINDING_URI, authn.getProtocolBinding());
}
@Test
public void createAuthenticationRequestWhenSetUnsupportredUriThenThrowsIllegalArgumentException() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("my-invalid-binding"));
factory.setProtocolBinding("my-invalid-binding");
this.exception.expect(IllegalArgumentException.class);
this.exception.expectMessage(containsString("my-invalid-binding"));
this.factory.setProtocolBinding("my-invalid-binding");
}
@Test
public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter =
mock(Converter.class);
when(authenticationRequestContextConverter.convert(this.context)).thenReturn(authnRequest());
Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
Converter.class);
given(authenticationRequestContextConverter.convert(this.context))
.willReturn(TestOpenSamlObjects.authnRequest());
this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
this.factory.createPostAuthenticationRequest(this.context);
@@ -194,9 +179,10 @@ public class OpenSamlAuthenticationRequestFactoryTests {
@Test
public void createRedirectAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter =
mock(Converter.class);
when(authenticationRequestContextConverter.convert(this.context)).thenReturn(authnRequest());
Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
Converter.class);
given(authenticationRequestContextConverter.convert(this.context))
.willReturn(TestOpenSamlObjects.authnRequest());
this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
this.factory.createRedirectAuthenticationRequest(this.context);
@@ -205,44 +191,45 @@ public class OpenSamlAuthenticationRequestFactoryTests {
@Test
public void setAuthenticationRequestContextConverterWhenNullThenException() {
assertThatCode(() -> this.factory.setAuthenticationRequestContextConverter(null))
.isInstanceOf(IllegalArgumentException.class);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.factory.setAuthenticationRequestContextConverter(null));
// @formatter:on
}
@Test
public void createPostAuthenticationRequestWhenAssertionConsumerServiceBindingThenUses() {
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder
.assertionConsumerServiceBinding(REDIRECT)
.build();
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
Saml2AuthenticationRequestContext context = this.contextBuilder
.relyingPartyRegistration(relyingPartyRegistration)
.build();
.relyingPartyRegistration(relyingPartyRegistration).build();
Saml2PostAuthenticationRequest request = this.factory.createPostAuthenticationRequest(context);
String samlRequest = request.getSamlRequest();
String inflated = new String(samlDecode(samlRequest));
String inflated = new String(Saml2Utils.samlDecode(samlRequest));
assertThat(inflated).contains("ProtocolBinding=\"" + SAMLConstants.SAML2_REDIRECT_BINDING_URI + "\"");
}
private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) {
AbstractSaml2AuthenticationRequest result = (binding == REDIRECT) ?
factory.createRedirectAuthenticationRequest(context) :
factory.createPostAuthenticationRequest(context);
AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT)
? this.factory.createRedirectAuthenticationRequest(this.context)
: this.factory.createPostAuthenticationRequest(this.context);
String samlRequest = result.getSamlRequest();
assertThat(samlRequest).isNotEmpty();
if (result.getBinding() == REDIRECT) {
samlRequest = samlInflate(samlDecode(samlRequest));
if (result.getBinding() == Saml2MessageBinding.REDIRECT) {
samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest));
}
else {
samlRequest = new String(samlDecode(samlRequest), UTF_8);
samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8);
}
try {
Document document = getParserPool().parse(
new ByteArrayInputStream(samlRequest.getBytes(UTF_8)));
Document document = XMLObjectProviderRegistrySupport.getParserPool()
.parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8)));
Element element = document.getDocumentElement();
return (AuthnRequest) this.unmarshaller.unmarshall(element);
}
catch (Exception e) {
throw new Saml2Exception(e);
catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
}
@@ -20,12 +20,10 @@ import java.util.UUID;
import org.junit.Test;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode;
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlInflate;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartySigningCredential;
/**
* Tests for {@link Saml2AuthenticationRequestFactory} default interface methods
@@ -34,40 +32,35 @@ public class Saml2AuthenticationRequestFactoryTests {
private RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id")
.assertionConsumerServiceUrlTemplate("template")
.providerDetails(c -> c.webSsoUrl("https://example.com/destination"))
.providerDetails(c -> c.entityId("remote-entity-id"))
.localEntityIdTemplate("local-entity-id")
.credentials(c -> c.add(relyingPartySigningCredential()))
.build();
.providerDetails((c) -> c.webSsoUrl("https://example.com/destination"))
.providerDetails((c) -> c.entityId("remote-entity-id")).localEntityIdTemplate("local-entity-id")
.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential())).build();
@Test
public void createAuthenticationRequestParametersWhenRedirectDefaultIsUsedMessageIsDeflatedAndEncoded() {
final String value = "Test String: "+ UUID.randomUUID().toString();
Saml2AuthenticationRequestFactory factory = request -> value;
final String value = "Test String: " + UUID.randomUUID().toString();
Saml2AuthenticationRequestFactory factory = (request) -> value;
Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder()
.relyingPartyRegistration(registration)
.issuer("https://example.com/issuer")
.assertionConsumerServiceUrl("https://example.com/acs-url")
.build();
.relyingPartyRegistration(this.registration).issuer("https://example.com/issuer")
.assertionConsumerServiceUrl("https://example.com/acs-url").build();
Saml2RedirectAuthenticationRequest response = factory.createRedirectAuthenticationRequest(request);
String resultValue = response.getSamlRequest();
byte[] decoded = samlDecode(resultValue);
String inflated = samlInflate(decoded);
byte[] decoded = Saml2Utils.samlDecode(resultValue);
String inflated = Saml2Utils.samlInflate(decoded);
assertThat(inflated).isEqualTo(value);
}
@Test
public void createAuthenticationRequestParametersWhenPostDefaultIsUsedMessageIsEncoded() {
final String value = "Test String: "+ UUID.randomUUID().toString();
Saml2AuthenticationRequestFactory factory = request -> value;
final String value = "Test String: " + UUID.randomUUID().toString();
Saml2AuthenticationRequestFactory factory = (request) -> value;
Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder()
.relyingPartyRegistration(registration)
.issuer("https://example.com/issuer")
.assertionConsumerServiceUrl("https://example.com/acs-url")
.build();
.relyingPartyRegistration(this.registration).issuer("https://example.com/issuer")
.assertionConsumerServiceUrl("https://example.com/acs-url").build();
Saml2PostAuthenticationRequest response = factory.createPostAuthenticationRequest(request);
String resultValue = response.getSamlRequest();
byte[] decoded = samlDecode(resultValue);
byte[] decoded = Saml2Utils.samlDecode(resultValue);
assertThat(new String(decoded)).isEqualTo(value);
}
}
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
@@ -32,6 +33,7 @@ import org.apache.xml.security.encryption.XMLCipherParameters;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.schema.XSAny;
import org.opensaml.core.xml.schema.XSBoolean;
@@ -49,6 +51,7 @@ import org.opensaml.core.xml.schema.impl.XSURIBuilder;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.common.SignableSAMLObject;
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;
@@ -82,22 +85,26 @@ import org.opensaml.xmlsec.signature.support.SignatureSupport;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2X509Credential;
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory;
import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS;
import static org.springframework.security.saml2.core.TestSaml2X509Credentials.assertingPartySigningCredential;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
public final class TestOpenSamlObjects {
static {
OpenSamlInitializationService.initialize();
}
private static String USERNAME = "test@saml.user";
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 static SecretKey SECRET_KEY =
new SecretKeySpec(Base64.getDecoder().decode("shOnwNMoCv88HKMEa91+FlYoD5RNvzMTAL5LGxZKIFk="), "AES");
private static SecretKey SECRET_KEY = new SecretKeySpec(
Base64.getDecoder().decode("shOnwNMoCv88HKMEa91+FlYoD5RNvzMTAL5LGxZKIFk="), "AES");
private TestOpenSamlObjects() {
}
static Response response() {
return response(DESTINATION, ASSERTING_PARTY_ENTITY_ID);
@@ -105,7 +112,7 @@ public final class TestOpenSamlObjects {
static Response response(String destination, String issuerEntityId) {
Response response = build(Response.DEFAULT_ELEMENT_NAME);
response.setID("R"+UUID.randomUUID().toString());
response.setID("R" + UUID.randomUUID().toString());
response.setIssueInstant(DateTime.now());
response.setVersion(SAMLVersion.VERSION_20);
response.setID("_" + UUID.randomUUID().toString());
@@ -117,28 +124,22 @@ public final class TestOpenSamlObjects {
static Response signedResponseWithOneAssertion() {
Response response = response();
response.getAssertions().add(assertion());
return signed(response, assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
return signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
}
static Assertion assertion() {
return assertion(USERNAME, ASSERTING_PARTY_ENTITY_ID, RELYING_PARTY_ENTITY_ID, DESTINATION);
}
static Assertion assertion(
String username,
String issuerEntityId,
String recipientEntityId,
String recipientUri
) {
static Assertion assertion(String username, String issuerEntityId, String recipientEntityId, String recipientUri) {
Assertion assertion = build(Assertion.DEFAULT_ELEMENT_NAME);
assertion.setID("A"+ UUID.randomUUID().toString());
assertion.setID("A" + UUID.randomUUID().toString());
assertion.setIssueInstant(DateTime.now());
assertion.setVersion(SAMLVersion.VERSION_20);
assertion.setIssueInstant(DateTime.now());
assertion.setIssuer(issuer(issuerEntityId));
assertion.setSubject(subject(username));
assertion.setConditions(conditions());
SubjectConfirmation subjectConfirmation = subjectConfirmation();
subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER);
SubjectConfirmationData confirmationData = subjectConfirmationData(recipientEntityId);
@@ -156,11 +157,9 @@ public final class TestOpenSamlObjects {
static Subject subject(String principalName) {
Subject subject = build(Subject.DEFAULT_ELEMENT_NAME);
if (principalName != null) {
subject.setNameID(nameId(principalName));
}
return subject;
}
@@ -206,7 +205,8 @@ public final class TestOpenSamlObjects {
return cred;
}
static Credential getSigningCredential(org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) {
static Credential getSigningCredential(
org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) {
BasicCredential cred = getBasicCredential(credential);
cred.setEntityId(entityId);
cred.setUsageType(UsageType.SIGNING);
@@ -214,17 +214,12 @@ public final class TestOpenSamlObjects {
}
static BasicCredential getBasicCredential(Saml2X509Credential credential) {
return CredentialSupport.getSimpleCredential(
credential.getCertificate(),
credential.getPrivateKey()
);
return CredentialSupport.getSimpleCredential(credential.getCertificate(), credential.getPrivateKey());
}
static BasicCredential getBasicCredential(org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
return CredentialSupport.getSimpleCredential(
credential.getCertificate(),
credential.getPrivateKey()
);
static BasicCredential getBasicCredential(
org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
return CredentialSupport.getSimpleCredential(credential.getCertificate(), credential.getPrivateKey());
}
static <T extends SignableSAMLObject> T signed(T signable, Saml2X509Credential credential, String entityId) {
@@ -236,14 +231,15 @@ public final class TestOpenSamlObjects {
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
try {
SignatureSupport.signObject(signable, parameters);
} catch (MarshallingException | SignatureException | SecurityException e) {
throw new Saml2Exception(e);
}
catch (MarshallingException | SignatureException | SecurityException ex) {
throw new Saml2Exception(ex);
}
return signable;
}
static <T extends SignableSAMLObject> T signed(T signable, org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) {
static <T extends SignableSAMLObject> T signed(T signable,
org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) {
SignatureSigningParameters parameters = new SignatureSigningParameters();
Credential signingCredential = getSigningCredential(credential, entityId);
parameters.setSigningCredential(signingCredential);
@@ -252,10 +248,10 @@ public final class TestOpenSamlObjects {
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
try {
SignatureSupport.signObject(signable, parameters);
} catch (MarshallingException | SignatureException | SecurityException e) {
throw new Saml2Exception(e);
}
catch (MarshallingException | SignatureException | SecurityException ex) {
throw new Saml2Exception(ex);
}
return signable;
}
@@ -265,19 +261,20 @@ public final class TestOpenSamlObjects {
try {
return encrypter.encrypt(assertion);
}
catch (EncryptionException e) {
throw new Saml2Exception("Unable to encrypt assertion.", e);
catch (EncryptionException ex) {
throw new Saml2Exception("Unable to encrypt assertion.", ex);
}
}
static EncryptedAssertion encrypted(Assertion assertion, org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
static EncryptedAssertion encrypted(Assertion assertion,
org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
X509Certificate certificate = credential.getCertificate();
Encrypter encrypter = getEncrypter(certificate);
try {
return encrypter.encrypt(assertion);
}
catch (EncryptionException e) {
throw new Saml2Exception("Unable to encrypt assertion.", e);
catch (EncryptionException ex) {
throw new Saml2Exception("Unable to encrypt assertion.", ex);
}
}
@@ -287,113 +284,100 @@ public final class TestOpenSamlObjects {
try {
return encrypter.encrypt(nameId);
}
catch (EncryptionException e) {
throw new Saml2Exception("Unable to encrypt nameID.", e);
catch (EncryptionException ex) {
throw new Saml2Exception("Unable to encrypt nameID.", ex);
}
}
static EncryptedID encrypted(NameID nameId, org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
static EncryptedID encrypted(NameID nameId,
org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
X509Certificate certificate = credential.getCertificate();
Encrypter encrypter = getEncrypter(certificate);
try {
return encrypter.encrypt(nameId);
}
catch (EncryptionException e) {
throw new Saml2Exception("Unable to encrypt nameID.", e);
catch (EncryptionException ex) {
throw new Saml2Exception("Unable to encrypt nameID.", ex);
}
}
private static Encrypter getEncrypter(X509Certificate certificate) {
String dataAlgorithm = XMLCipherParameters.AES_256;
String keyAlgorithm = XMLCipherParameters.RSA_1_5;
BasicCredential dataCredential = new BasicCredential(SECRET_KEY);
DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters();
dataEncryptionParameters.setEncryptionCredential(dataCredential);
dataEncryptionParameters.setAlgorithm(dataAlgorithm);
Credential credential = CredentialSupport.getSimpleCredential(certificate, null);
KeyEncryptionParameters keyEncryptionParameters = new KeyEncryptionParameters();
keyEncryptionParameters.setEncryptionCredential(credential);
keyEncryptionParameters.setAlgorithm(keyAlgorithm);
Encrypter encrypter = new Encrypter(dataEncryptionParameters, keyEncryptionParameters);
Encrypter.KeyPlacement keyPlacement = Encrypter.KeyPlacement.valueOf("PEER");
encrypter.setKeyPlacement(keyPlacement);
return encrypter;
}
static List<AttributeStatement> attributeStatements() {
List<AttributeStatement> attributeStatements = new ArrayList<>();
AttributeStatementBuilder attributeStatementBuilder = new AttributeStatementBuilder();
AttributeBuilder attributeBuilder = new AttributeBuilder();
AttributeStatement attrStmt1 = attributeStatementBuilder.buildObject();
Attribute emailAttr = attributeBuilder.buildObject();
emailAttr.setName("email");
XSAny email1 = new XSAnyBuilder()
.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); // gh-8864
XSAny email1 = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); // gh-8864
email1.setTextContent("john.doe@example.com");
emailAttr.getAttributeValues().add(email1);
XSAny email2 = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME);
email2.setTextContent("doe.john@example.com");
emailAttr.getAttributeValues().add(email2);
attrStmt1.getAttributes().add(emailAttr);
Attribute nameAttr = attributeBuilder.buildObject();
nameAttr.setName("name");
XSString name = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
name.setValue("John Doe");
nameAttr.getAttributeValues().add(name);
attrStmt1.getAttributes().add(nameAttr);
Attribute ageAttr = attributeBuilder.buildObject();
ageAttr.setName("age");
XSInteger age = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME);
age.setValue(21);
ageAttr.getAttributeValues().add(age);
attrStmt1.getAttributes().add(ageAttr);
attributeStatements.add(attrStmt1);
AttributeStatement attrStmt2 = attributeStatementBuilder.buildObject();
Attribute websiteAttr = attributeBuilder.buildObject();
websiteAttr.setName("website");
XSURI uri = new XSURIBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSURI.TYPE_NAME);
uri.setValue("https://johndoe.com/");
websiteAttr.getAttributeValues().add(uri);
attrStmt2.getAttributes().add(websiteAttr);
Attribute registeredAttr = attributeBuilder.buildObject();
registeredAttr.setName("registered");
XSBoolean registered = new XSBooleanBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBoolean.TYPE_NAME);
XSBoolean registered = new XSBooleanBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
XSBoolean.TYPE_NAME);
registered.setValue(new XSBooleanValue(true, false));
registeredAttr.getAttributeValues().add(registered);
attrStmt2.getAttributes().add(registeredAttr);
Attribute registeredDateAttr = attributeBuilder.buildObject();
registeredDateAttr.setName("registeredDate");
XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSDateTime.TYPE_NAME);
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);
attrStmt2.getAttributes().add(registeredDateAttr);
attributeStatements.add(attrStmt2);
return attributeStatements;
}
static ValidationContext validationContext() {
Map<String, Object> params = new HashMap<>();
params.put(SC_VALID_RECIPIENTS, Collections.singleton(DESTINATION));
params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(DESTINATION));
return new ValidationContext(params);
}
static <T extends XMLObject> T build(QName qName) {
return (T) getBuilderFactory().getBuilder(qName).buildObject(qName);
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}
}
@@ -16,17 +16,20 @@
package org.springframework.security.saml2.provider.service.authentication;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.relyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
/**
* Test {@link Saml2AuthenticationRequestContext}s
*/
public class TestSaml2AuthenticationRequestContexts {
public final class TestSaml2AuthenticationRequestContexts {
private TestSaml2AuthenticationRequestContexts() {
}
public static Saml2AuthenticationRequestContext.Builder authenticationRequestContext() {
return Saml2AuthenticationRequestContext.builder()
.relayState("relayState")
.issuer("issuer")
.relyingPartyRegistration(relyingPartyRegistration().build())
return Saml2AuthenticationRequestContext.builder().relayState("relayState").issuer("issuer")
.relyingPartyRegistration(TestRelyingPartyRegistrations.relyingPartyRegistration().build())
.assertionConsumerServiceUrl("assertionConsumerServiceUrl");
}
}
@@ -18,13 +18,12 @@ package org.springframework.security.saml2.provider.service.metadata;
import org.junit.Test;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.saml2.core.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.full;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.noCredentials;
/**
* Tests for {@link OpenSamlMetadataResolver}
@@ -33,21 +32,12 @@ public class OpenSamlMetadataResolverTests {
@Test
public void resolveWhenRelyingPartyThenMetadataMatches() {
// given
RelyingPartyRegistration relyingPartyRegistration = full()
.assertionConsumerServiceBinding(REDIRECT)
.build();
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
// when
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
// then
assertThat(metadata)
.contains("<EntityDescriptor")
.contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"")
.contains("<md:KeyDescriptor use=\"signing\">")
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"").contains("<md:KeyDescriptor use=\"signing\">")
.contains("<md:KeyDescriptor use=\"encryption\">")
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
@@ -56,25 +46,17 @@ public class OpenSamlMetadataResolverTests {
@Test
public void resolveWhenRelyingPartyNoCredentialsThenMetadataMatches() {
// given
RelyingPartyRegistration relyingPartyRegistration = noCredentials()
.assertingPartyDetails(party -> party
.verificationX509Credentials(c -> c.add(relyingPartyVerifyingCredential()))
)
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
// when
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
// then
assertThat(metadata)
.contains("<EntityDescriptor")
.contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"")
.doesNotContain("<md:KeyDescriptor use=\"signing\">")
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"").doesNotContain("<md:KeyDescriptor use=\"signing\">")
.doesNotContain("<md:KeyDescriptor use=\"encryption\">")
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"")
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"");
}
}
@@ -25,38 +25,32 @@ import java.util.Base64;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.security.saml2.Saml2Exception;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.springframework.http.HttpStatus.OK;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests {
private static final String CERTIFICATE =
"MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk";
private static final String ENTITY_DESCRIPTOR_TEMPLATE =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" " +
"entityID=\"entity-id\" " +
"ID=\"_bf133aac099b99b3d81286e1a341f2d34188043a77fe15bf4bf1487dae9b2ea3\">\n%s" +
"</md:EntityDescriptor>";
private static final String IDP_SSO_DESCRIPTOR_TEMPLATE =
"<md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n" +
"%s\n" +
"</md:IDPSSODescriptor>";
private static final String KEY_DESCRIPTOR_TEMPLATE =
"<md:KeyDescriptor %s>\n" +
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
"<ds:X509Data>\n" +
"<ds:X509Certificate>" + CERTIFICATE + "</ds:X509Certificate>\n" +
"</ds:X509Data>\n" +
"</ds:KeyInfo>\n" +
"</md:KeyDescriptor>";
private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE =
"<md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" " +
"Location=\"sso-location\"/>";
private static final String CERTIFICATE = "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk";
private static final String ENTITY_DESCRIPTOR_TEMPLATE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" " + "entityID=\"entity-id\" "
+ "ID=\"_bf133aac099b99b3d81286e1a341f2d34188043a77fe15bf4bf1487dae9b2ea3\">\n%s"
+ "</md:EntityDescriptor>";
private static final String IDP_SSO_DESCRIPTOR_TEMPLATE = "<md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"
+ "%s\n" + "</md:IDPSSODescriptor>";
private static final String KEY_DESCRIPTOR_TEMPLATE = "<md:KeyDescriptor %s>\n"
+ "<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" + "<ds:X509Data>\n"
+ "<ds:X509Certificate>" + CERTIFICATE + "</ds:X509Certificate>\n" + "</ds:X509Data>\n" + "</ds:KeyInfo>\n"
+ "</md:KeyDescriptor>";
private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE = "<md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" "
+ "Location=\"sso-location\"/>";
private OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter converter;
@@ -67,50 +61,45 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests {
@Test
public void readWhenMissingIDPSSODescriptorThenException() {
MockClientHttpResponse response = new MockClientHttpResponse
((String.format(ENTITY_DESCRIPTOR_TEMPLATE, "")).getBytes(), OK);
assertThatCode(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.isInstanceOf(Saml2Exception.class)
.hasMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element");
MockClientHttpResponse response = new MockClientHttpResponse(
(String.format(ENTITY_DESCRIPTOR_TEMPLATE, "")).getBytes(), HttpStatus.OK);
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.withMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element");
}
@Test
public void readWhenMissingVerificationKeyThenException() {
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, ""));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), OK);
assertThatCode(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.isInstanceOf(Saml2Exception.class)
.hasMessageContaining("Metadata response is missing verification certificates, necessary for verifying SAML assertions");
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, ""));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), HttpStatus.OK);
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.withMessageContaining(
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
}
@Test
public void readWhenMissingSingleSignOnServiceThenException() {
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), OK);
assertThatCode(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.isInstanceOf(Saml2Exception.class)
.hasMessageContaining("Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), HttpStatus.OK);
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> this.converter.read(RelyingPartyRegistration.Builder.class, response))
.withMessageContaining(
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
}
@Test
public void readWhenDescriptorFullySpecifiedThenConfigures() throws Exception {
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"") +
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") +
String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)
));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), OK);
RelyingPartyRegistration registration =
this.converter.read(RelyingPartyRegistration.Builder.class, response)
.registrationId("one")
.build();
RelyingPartyRegistration.AssertingPartyDetails details =
registration.getAssertingPartyDetails();
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
+ String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"")
+ String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), HttpStatus.OK);
RelyingPartyRegistration registration = this.converter.read(RelyingPartyRegistration.Builder.class, response)
.registrationId("one").build();
RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails();
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
@@ -125,18 +114,12 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests {
@Test
public void readWhenKeyDescriptorHasNoUseThenConfiguresBothKeyTypes() throws Exception {
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
String.format(KEY_DESCRIPTOR_TEMPLATE, "") +
String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)
));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), OK);
RelyingPartyRegistration registration =
this.converter.read(RelyingPartyRegistration.Builder.class, response)
.registrationId("one")
.build();
RelyingPartyRegistration.AssertingPartyDetails details =
registration.getAssertingPartyDetails();
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
String.format(KEY_DESCRIPTOR_TEMPLATE, "") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
MockClientHttpResponse response = new MockClientHttpResponse(payload.getBytes(), HttpStatus.OK);
RelyingPartyRegistration registration = this.converter.read(RelyingPartyRegistration.Builder.class, response)
.registrationId("one").build();
RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails();
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
.isEqualTo(x509Certificate(CERTIFICATE));
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
@@ -147,10 +130,11 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests {
X509Certificate x509Certificate(String data) {
try {
InputStream certificate = new ByteArrayInputStream(Base64.getDecoder().decode(data.getBytes()));
return (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(certificate);
} catch (Exception e) {
throw new IllegalArgumentException(e);
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificate);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
@@ -18,33 +18,26 @@ package org.springframework.security.saml2.provider.service.registration;
import org.junit.Test;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRegistrationId;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.relyingPartyRegistration;
public class RelyingPartyRegistrationTests {
@Test
public void withRelyingPartyRegistrationWorks() {
RelyingPartyRegistration registration = relyingPartyRegistration()
.providerDetails(p -> p.binding(POST))
.providerDetails(p -> p.signAuthNRequest(false))
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
.build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.providerDetails((p) -> p.binding(Saml2MessageBinding.POST))
.providerDetails((p) -> p.signAuthNRequest(false))
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
RelyingPartyRegistration copy = RelyingPartyRegistration.withRelyingPartyRegistration(registration).build();
compareRegistrations(registration, copy);
}
private void compareRegistrations(RelyingPartyRegistration registration, RelyingPartyRegistration copy) {
assertThat(copy.getRegistrationId())
.isEqualTo(registration.getRegistrationId())
.isEqualTo("simplesamlphp");
assertThat(copy.getProviderDetails().getEntityId())
.isEqualTo(registration.getProviderDetails().getEntityId())
assertThat(copy.getRegistrationId()).isEqualTo(registration.getRegistrationId()).isEqualTo("simplesamlphp");
assertThat(copy.getProviderDetails().getEntityId()).isEqualTo(registration.getProviderDetails().getEntityId())
.isEqualTo(copy.getAssertingPartyDetails().getEntityId())
.isEqualTo(registration.getAssertingPartyDetails().getEntityId())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
@@ -53,38 +46,27 @@ public class RelyingPartyRegistrationTests {
.isEqualTo(copy.getAssertionConsumerServiceLocation())
.isEqualTo(registration.getAssertionConsumerServiceLocation())
.isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
assertThat(copy.getCredentials())
.containsAll(registration.getCredentials())
.containsExactly(
registration.getCredentials().get(0),
registration.getCredentials().get(1)
);
assertThat(copy.getLocalEntityIdTemplate())
.isEqualTo(registration.getLocalEntityIdTemplate())
.isEqualTo(copy.getEntityId())
.isEqualTo(registration.getEntityId())
assertThat(copy.getCredentials()).containsAll(registration.getCredentials())
.containsExactly(registration.getCredentials().get(0), registration.getCredentials().get(1));
assertThat(copy.getLocalEntityIdTemplate()).isEqualTo(registration.getLocalEntityIdTemplate())
.isEqualTo(copy.getEntityId()).isEqualTo(registration.getEntityId())
.isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}");
assertThat(copy.getProviderDetails().getWebSsoUrl())
.isEqualTo(registration.getProviderDetails().getWebSsoUrl())
assertThat(copy.getProviderDetails().getWebSsoUrl()).isEqualTo(registration.getProviderDetails().getWebSsoUrl())
.isEqualTo(copy.getAssertingPartyDetails().getSingleSignOnServiceLocation())
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php");
assertThat(copy.getProviderDetails().getBinding())
.isEqualTo(registration.getProviderDetails().getBinding())
assertThat(copy.getProviderDetails().getBinding()).isEqualTo(registration.getProviderDetails().getBinding())
.isEqualTo(copy.getAssertingPartyDetails().getSingleSignOnServiceBinding())
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding())
.isEqualTo(POST);
.isEqualTo(Saml2MessageBinding.POST);
assertThat(copy.getProviderDetails().isSignAuthNRequest())
.isEqualTo(registration.getProviderDetails().isSignAuthNRequest())
.isEqualTo(copy.getAssertingPartyDetails().getWantAuthnRequestsSigned())
.isEqualTo(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned())
.isFalse();
.isEqualTo(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()).isFalse();
assertThat(copy.getAssertionConsumerServiceBinding())
.isEqualTo(registration.getAssertionConsumerServiceBinding());
assertThat(copy.getDecryptionX509Credentials())
.isEqualTo(registration.getDecryptionX509Credentials());
assertThat(copy.getSigningX509Credentials())
.isEqualTo(registration.getSigningX509Credentials());
assertThat(copy.getDecryptionX509Credentials()).isEqualTo(registration.getDecryptionX509Credentials());
assertThat(copy.getSigningX509Credentials()).isEqualTo(registration.getSigningX509Credentials());
assertThat(copy.getAssertingPartyDetails().getEncryptionX509Credentials())
.isEqualTo(registration.getAssertingPartyDetails().getEncryptionX509Credentials());
assertThat(copy.getAssertingPartyDetails().getVerificationX509Credentials())
@@ -93,16 +75,12 @@ public class RelyingPartyRegistrationTests {
@Test
public void buildWhenUsingDefaultsThenAssertionConsumerServiceBindingDefaultsToPost() {
RelyingPartyRegistration relyingPartyRegistration = withRegistrationId("id")
.entityId("entity-id")
.assertionConsumerServiceLocation("location")
.assertingPartyDetails(assertingParty -> assertingParty
.entityId("entity-id")
.singleSignOnServiceLocation("location"))
.credentials(c -> c.add(relyingPartyVerifyingCredential()))
.build();
assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding())
.isEqualTo(POST);
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("id")
.entityId("entity-id").assertionConsumerServiceLocation("location")
.assertingPartyDetails((assertingParty) -> assertingParty.entityId("entity-id")
.singleSignOnServiceLocation("location"))
.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())).build();
assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
}
}
@@ -23,114 +23,78 @@ import org.junit.Test;
import org.springframework.security.saml2.Saml2Exception;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link RelyingPartyRegistration}
*/
public class RelyingPartyRegistrationsTests {
private static final String IDP_SSO_DESCRIPTOR_PAYLOAD =
"<md:EntityDescriptor entityID=\"https://idp.example.com/idp/shibboleth\"\n" +
" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xmlns:shibmd=\"urn:mace:shibboleth:metadata:1.0\"\n" +
" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" +
" xmlns:mdui=\"urn:oasis:names:tc:SAML:metadata:ui\">\n" +
" \n" +
" <md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n" +
" <md:Extensions>\n" +
" <shibmd:Scope regexp=\"false\">example.com</shibmd:Scope>\n" +
" \n" +
" <mdui:UIInfo>\n" +
" <mdui:DisplayName xml:lang=\"en\">\n" +
" Consortium GARR IdP\n" +
" </mdui:DisplayName>\n" +
" <mdui:DisplayName xml:lang=\"it\">\n" +
" Consortium GARR IdP\n" +
" </mdui:DisplayName>\n" +
" \n" +
" <mdui:Description xml:lang=\"en\">\n" +
" This Identity Provider gives support for the Consortium GARR's user community\n" +
" </mdui:Description>\n" +
" <mdui:Description xml:lang=\"it\">\n" +
" Questo Identity Provider di test fornisce supporto alla comunita' utenti GARR\n" +
" </mdui:Description>\n" +
" </mdui:UIInfo>\n" +
" </md:Extensions>\n" +
" \n" +
" <md:KeyDescriptor>\n" +
" <ds:KeyInfo>\n" +
" <ds:X509Data>\n" +
" <ds:X509Certificate>\n" +
" MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB\n" +
" BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe\n" +
" Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t\n" +
" cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP\n" +
" ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS\n" +
" v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN\n" +
" iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece\n" +
" byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz\n" +
" cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v\n" +
" dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX\n" +
" gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w\n" +
" dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW\n" +
" BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu\n" +
" 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL\n" +
" qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU\n" +
" duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU\n" +
" yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p\n" +
" V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e\n" +
" Cq53OZt9ISjHEw==\n" +
" </ds:X509Certificate>\n" +
" </ds:X509Data>\n" +
" </ds:KeyInfo>\n" +
" </md:KeyDescriptor>\n" +
" \n" +
" <md:SingleSignOnService\n" +
" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
" Location=\"https://idp.example.com/idp/profile/SAML2/POST/SSO\"/>\n" +
" </md:IDPSSODescriptor>\n" +
" \n" +
" <md:Organization>\n" +
" <md:OrganizationName xml:lang=\"en\">\n" +
" Consortium GARR\n" +
" </md:OrganizationName>\n" +
" <md:OrganizationName xml:lang=\"it\">\n" +
" Consortium GARR\n" +
" </md:OrganizationName>\n" +
" \n" +
" <md:OrganizationDisplayName xml:lang=\"en\">\n" +
" Consortium GARR\n" +
" </md:OrganizationDisplayName>\n" +
" <md:OrganizationDisplayName xml:lang=\"it\">\n" +
" Consortium GARR\n" +
" </md:OrganizationDisplayName>\n" +
" \n" +
" <md:OrganizationURL xml:lang=\"it\">\n" +
" https://example.org\n" +
" </md:OrganizationURL>\n" +
" </md:Organization>\n" +
" \n" +
" <md:ContactPerson contactType=\"technical\">\n" +
" <md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>\n" +
" </md:ContactPerson>\n" +
" \n" +
"</md:EntityDescriptor>";
private static final String IDP_SSO_DESCRIPTOR_PAYLOAD = "<md:EntityDescriptor entityID=\"https://idp.example.com/idp/shibboleth\"\n"
+ " xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xmlns:shibmd=\"urn:mace:shibboleth:metadata:1.0\"\n"
+ " xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n"
+ " xmlns:mdui=\"urn:oasis:names:tc:SAML:metadata:ui\">\n" + " \n"
+ " <md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"
+ " <md:Extensions>\n" + " <shibmd:Scope regexp=\"false\">example.com</shibmd:Scope>\n"
+ " \n" + " <mdui:UIInfo>\n" + " <mdui:DisplayName xml:lang=\"en\">\n"
+ " Consortium GARR IdP\n" + " </mdui:DisplayName>\n"
+ " <mdui:DisplayName xml:lang=\"it\">\n" + " Consortium GARR IdP\n"
+ " </mdui:DisplayName>\n" + " \n" + " <mdui:Description xml:lang=\"en\">\n"
+ " This Identity Provider gives support for the Consortium GARR's user community\n"
+ " </mdui:Description>\n" + " <mdui:Description xml:lang=\"it\">\n"
+ " Questo Identity Provider di test fornisce supporto alla comunita' utenti GARR\n"
+ " </mdui:Description>\n" + " </mdui:UIInfo>\n" + " </md:Extensions>\n" + " \n"
+ " <md:KeyDescriptor>\n" + " <ds:KeyInfo>\n" + " <ds:X509Data>\n"
+ " <ds:X509Certificate>\n"
+ " MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB\n"
+ " BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe\n"
+ " Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t\n"
+ " cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP\n"
+ " ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS\n"
+ " v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN\n"
+ " iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece\n"
+ " byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz\n"
+ " cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v\n"
+ " dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX\n"
+ " gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w\n"
+ " dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW\n"
+ " BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu\n"
+ " 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL\n"
+ " qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU\n"
+ " duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU\n"
+ " yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p\n"
+ " V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e\n"
+ " Cq53OZt9ISjHEw==\n" + " </ds:X509Certificate>\n"
+ " </ds:X509Data>\n" + " </ds:KeyInfo>\n" + " </md:KeyDescriptor>\n" + " \n"
+ " <md:SingleSignOnService\n"
+ " Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
+ " Location=\"https://idp.example.com/idp/profile/SAML2/POST/SSO\"/>\n"
+ " </md:IDPSSODescriptor>\n" + " \n" + " <md:Organization>\n"
+ " <md:OrganizationName xml:lang=\"en\">\n" + " Consortium GARR\n"
+ " </md:OrganizationName>\n" + " <md:OrganizationName xml:lang=\"it\">\n"
+ " Consortium GARR\n" + " </md:OrganizationName>\n" + " \n"
+ " <md:OrganizationDisplayName xml:lang=\"en\">\n" + " Consortium GARR\n"
+ " </md:OrganizationDisplayName>\n" + " <md:OrganizationDisplayName xml:lang=\"it\">\n"
+ " Consortium GARR\n" + " </md:OrganizationDisplayName>\n" + " \n"
+ " <md:OrganizationURL xml:lang=\"it\">\n" + " https://example.org\n"
+ " </md:OrganizationURL>\n" + " </md:Organization>\n" + " \n"
+ " <md:ContactPerson contactType=\"technical\">\n"
+ " <md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>\n"
+ " </md:ContactPerson>\n" + " \n" + "</md:EntityDescriptor>";
@Test
public void fromMetadataLocationWhenResolvableThenPopulatesBuilder() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(server.url("/").toString())
.entityId("rp")
.build();
.fromMetadataLocation(server.url("/").toString()).entityId("rp").build();
RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails();
assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth");
assertThat(details.getSingleSignOnServiceLocation())
.isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO");
assertThat(details.getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
assertThat(details.getVerificationX509Credentials()).hasSize(1);
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
}
@@ -142,8 +106,8 @@ public class RelyingPartyRegistrationsTests {
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
String url = server.url("/").toString();
server.shutdown();
assertThatCode(() -> RelyingPartyRegistrations.fromMetadataLocation(url))
.isInstanceOf(Saml2Exception.class);
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> RelyingPartyRegistrations.fromMetadataLocation(url));
}
}
@@ -152,8 +116,9 @@ public class RelyingPartyRegistrationsTests {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setBody("malformed").setResponseCode(200));
String url = server.url("/").toString();
assertThatCode(() -> RelyingPartyRegistrations.fromMetadataLocation(url))
.isInstanceOf(Saml2Exception.class);
assertThatExceptionOfType(Saml2Exception.class)
.isThrownBy(() -> RelyingPartyRegistrations.fromMetadataLocation(url));
}
}
}
@@ -16,57 +16,49 @@
package org.springframework.security.saml2.provider.service.registration;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.credentials.Saml2X509Credential;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartySigningCredential;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
/**
* Preconfigured test data for {@link RelyingPartyRegistration} objects
*/
public class TestRelyingPartyRegistrations {
public final class TestRelyingPartyRegistrations {
private TestRelyingPartyRegistrations() {
}
public static RelyingPartyRegistration.Builder relyingPartyRegistration() {
String registrationId = "simplesamlphp";
String rpEntityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
Saml2X509Credential signingCredential = relyingPartySigningCredential();
String assertionConsumerServiceLocation = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
Saml2X509Credential signingCredential = TestSaml2X509Credentials.relyingPartySigningCredential();
String assertionConsumerServiceLocation = "{baseUrl}"
+ Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
String apEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
Saml2X509Credential verificationCertificate = relyingPartyVerifyingCredential();
Saml2X509Credential verificationCertificate = TestSaml2X509Credentials.relyingPartyVerifyingCredential();
String singleSignOnServiceLocation = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
return RelyingPartyRegistration.withRegistrationId(registrationId)
.entityId(rpEntityId)
return RelyingPartyRegistration.withRegistrationId(registrationId).entityId(rpEntityId)
.assertionConsumerServiceLocation(assertionConsumerServiceLocation)
.credentials(c -> c.add(signingCredential))
.providerDetails(c -> c
.entityId(apEntityId)
.webSsoUrl(singleSignOnServiceLocation))
.credentials(c -> c.add(verificationCertificate));
.credentials((c) -> c.add(signingCredential))
.providerDetails((c) -> c.entityId(apEntityId).webSsoUrl(singleSignOnServiceLocation))
.credentials((c) -> c.add(verificationCertificate));
}
public static RelyingPartyRegistration.Builder noCredentials() {
return RelyingPartyRegistration.withRegistrationId("registration-id")
.entityId("rp-entity-id")
.assertionConsumerServiceLocation("https://rp.example.org/acs")
.assertingPartyDetails(party -> party
.entityId("ap-entity-id")
.singleSignOnServiceLocation("https://ap.example.org/sso")
);
return RelyingPartyRegistration.withRegistrationId("registration-id").entityId("rp-entity-id")
.assertionConsumerServiceLocation("https://rp.example.org/acs").assertingPartyDetails((party) -> party
.entityId("ap-entity-id").singleSignOnServiceLocation("https://ap.example.org/sso"));
}
public static RelyingPartyRegistration.Builder full() {
return noCredentials()
.signingX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential()))
.decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential()))
.assertingPartyDetails(party -> party
.verificationX509Credentials(c -> c.add(
TestSaml2X509Credentials.relyingPartyVerifyingCredential())
)
);
.signingX509Credentials((c) -> c.add(org.springframework.security.saml2.core.TestSaml2X509Credentials
.relyingPartySigningCredential()))
.decryptionX509Credentials((c) -> c.add(org.springframework.security.saml2.core.TestSaml2X509Credentials
.relyingPartyDecryptingCredential()))
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(org.springframework.security.saml2.core.TestSaml2X509Credentials
.relyingPartyVerifyingCredential())));
}
}
@@ -16,28 +16,32 @@
package org.springframework.security.saml2.provider.service.servlet.filter;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import javax.servlet.http.HttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class Saml2WebSsoAuthenticationFilterTests {
private Saml2WebSsoAuthenticationFilter filter;
private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
private MockHttpServletRequest request = new MockHttpServletRequest();
private HttpServletResponse response = new MockHttpServletResponse();
@Rule
@@ -45,51 +49,50 @@ public class Saml2WebSsoAuthenticationFilterTests {
@Before
public void setup() {
filter = new Saml2WebSsoAuthenticationFilter(repository);
request.setPathInfo("/login/saml2/sso/idp-registration-id");
request.setParameter("SAMLResponse", "xml-data-goes-here");
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository);
this.request.setPathInfo("/login/saml2/sso/idp-registration-id");
this.request.setParameter("SAMLResponse", "xml-data-goes-here");
}
@Test
public void constructingFilterWithMissingRegistrationIdVariableThenThrowsException() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("filterProcessesUrl must contain a {registrationId} match variable");
filter = new Saml2WebSsoAuthenticationFilter(repository, "/url/missing/variable");
this.exception.expect(IllegalArgumentException.class);
this.exception.expectMessage("filterProcessesUrl must contain a {registrationId} match variable");
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository, "/url/missing/variable");
}
@Test
public void constructingFilterWithValidRegistrationIdVariableThenSucceeds() {
filter = new Saml2WebSsoAuthenticationFilter(repository, "/url/variable/is/present/{registrationId}");
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository, "/url/variable/is/present/{registrationId}");
}
@Test
public void requiresAuthenticationWhenHappyPathThenReturnsTrue() {
Assert.assertTrue(filter.requiresAuthentication(request, response));
Assert.assertTrue(this.filter.requiresAuthentication(this.request, this.response));
}
@Test
public void requiresAuthenticationWhenCustomProcessingUrlThenReturnsTrue() {
filter = new Saml2WebSsoAuthenticationFilter(repository, "/some/other/path/{registrationId}");
request.setPathInfo("/some/other/path/idp-registration-id");
request.setParameter("SAMLResponse", "xml-data-goes-here");
Assert.assertTrue(filter.requiresAuthentication(request, response));
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository, "/some/other/path/{registrationId}");
this.request.setPathInfo("/some/other/path/idp-registration-id");
this.request.setParameter("SAMLResponse", "xml-data-goes-here");
Assert.assertTrue(this.filter.requiresAuthentication(this.request, this.response));
}
@Test
public void attemptAuthenticationWhenRegistrationIdDoesNotExistThenThrowsException() {
when(repository.findByRegistrationId("non-existent-id")).thenReturn(null);
filter = new Saml2WebSsoAuthenticationFilter(repository, "/some/other/path/{registrationId}");
request.setPathInfo("/some/other/path/non-existent-id");
request.setParameter("SAMLResponse", "response");
given(this.repository.findByRegistrationId("non-existent-id")).willReturn(null);
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository, "/some/other/path/{registrationId}");
this.request.setPathInfo("/some/other/path/non-existent-id");
this.request.setParameter("SAMLResponse", "response");
try {
filter.attemptAuthentication(request, response);
this.filter.attemptAuthentication(this.request, this.response);
failBecauseExceptionWasNotThrown(Saml2AuthenticationException.class);
} catch (Exception e) {
assertThat(e).isInstanceOf(Saml2AuthenticationException.class);
assertThat(e.getMessage()).isEqualTo("No relying party registration found");
}
catch (Exception ex) {
assertThat(ex).isInstanceOf(Saml2AuthenticationException.class);
assertThat(ex.getMessage()).isEqualTo("No relying party registration found");
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.servlet.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletException;
import org.junit.Before;
@@ -26,158 +27,136 @@ import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationRequestContexts;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
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.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.assertingPartyPrivateCredential;
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationRequestContexts.authenticationRequestContext;
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
public class Saml2WebSsoAuthenticationRequestFilterTests {
private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO";
private Saml2WebSsoAuthenticationRequestFilter filter;
private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
private Saml2AuthenticationRequestFactory factory = mock(Saml2AuthenticationRequestFactory.class);
private Saml2AuthenticationRequestContextResolver resolver =
mock(Saml2AuthenticationRequestContextResolver.class);
private Saml2AuthenticationRequestContextResolver resolver = mock(Saml2AuthenticationRequestContextResolver.class);
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain filterChain;
private RelyingPartyRegistration.Builder rpBuilder;
@Before
public void setup() {
filter = new Saml2WebSsoAuthenticationRequestFilter(repository);
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setPathInfo("/saml2/authenticate/registration-id");
filterChain = new MockFilterChain();
rpBuilder = RelyingPartyRegistration
.withRegistrationId("registration-id")
.providerDetails(c -> c.entityId("idp-entity-id"))
.providerDetails(c -> c.webSsoUrl(IDP_SSO_URL))
this.filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.request.setPathInfo("/saml2/authenticate/registration-id");
this.filterChain = new MockFilterChain();
this.rpBuilder = RelyingPartyRegistration.withRegistrationId("registration-id")
.providerDetails((c) -> c.entityId("idp-entity-id")).providerDetails((c) -> c.webSsoUrl(IDP_SSO_URL))
.assertionConsumerServiceUrlTemplate("template")
.credentials(c -> c.add(assertingPartyPrivateCredential()));
.credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()));
}
@Test
public void doFilterWhenNoRelayStateThenRedirectDoesNotContainParameter() throws ServletException, IOException {
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location"))
.doesNotContain("RelayState=")
.startsWith(IDP_SSO_URL);
given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).doesNotContain("RelayState=").startsWith(IDP_SSO_URL);
}
@Test
public void doFilterWhenRelayStateThenRedirectDoesContainParameter() throws ServletException, IOException {
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
request.setParameter("RelayState", "my-relay-state");
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location"))
.contains("RelayState=my-relay-state")
.startsWith(IDP_SSO_URL);
given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
this.request.setParameter("RelayState", "my-relay-state");
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).contains("RelayState=my-relay-state").startsWith(IDP_SSO_URL);
}
@Test
public void doFilterWhenRelayStateThatRequiresEncodingThenRedirectDoesContainsEncodedParameter() throws Exception {
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
request.setParameter("RelayState", relayStateValue);
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location"))
.contains("RelayState="+relayStateEncoded)
this.request.setParameter("RelayState", relayStateValue);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded)
.startsWith(IDP_SSO_URL);
}
@Test
public void doFilterWhenSimpleSignatureSpecifiedThenSignatureParametersAreInTheRedirectURL() throws Exception {
when(repository.findByRegistrationId("registration-id")).thenReturn(
rpBuilder
.build()
);
given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
request.setParameter("RelayState", relayStateValue);
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location"))
.contains("RelayState="+relayStateEncoded)
.contains("SigAlg=")
.contains("Signature=")
.startsWith(IDP_SSO_URL);
this.request.setParameter("RelayState", relayStateValue);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded).contains("SigAlg=")
.contains("Signature=").startsWith(IDP_SSO_URL);
}
@Test
public void doFilterWhenSignatureIsDisabledThenSignatureParametersAreNotInTheRedirectURL() throws Exception {
when(repository.findByRegistrationId("registration-id")).thenReturn(
rpBuilder
.providerDetails(c -> c.signAuthNRequest(false))
.build()
);
given(this.repository.findByRegistrationId("registration-id"))
.willReturn(this.rpBuilder.providerDetails((c) -> c.signAuthNRequest(false)).build());
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
request.setParameter("RelayState", relayStateValue);
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location"))
.contains("RelayState="+relayStateEncoded)
.doesNotContain("SigAlg=")
.doesNotContain("Signature=")
.startsWith(IDP_SSO_URL);
this.request.setParameter("RelayState", relayStateValue);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded)
.doesNotContain("SigAlg=").doesNotContain("Signature=").startsWith(IDP_SSO_URL);
}
@Test
public void doFilterWhenPostFormDataIsPresent() throws Exception {
when(repository.findByRegistrationId("registration-id")).thenReturn(
rpBuilder
.providerDetails(c -> c.binding(POST))
.build()
);
given(this.repository.findByRegistrationId("registration-id"))
.willReturn(this.rpBuilder.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build());
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param&javascript{alert('1');}";
final String relayStateEncoded = HtmlUtils.htmlEscape(relayStateValue);
request.setParameter("RelayState", relayStateValue);
filter.doFilterInternal(request, response, filterChain);
assertThat(response.getHeader("Location")).isNull();
assertThat(response.getContentAsString())
this.request.setParameter("RelayState", relayStateValue);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).isNull();
assertThat(this.response.getContentAsString())
.contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\"")
.contains("value=\""+relayStateEncoded+"\"");
.contains("value=\"" + relayStateEncoded + "\"");
}
@Test
public void doFilterWhenSetAuthenticationRequestFactoryThenUses() throws Exception {
RelyingPartyRegistration relyingParty = this.rpBuilder
.providerDetails(c -> c.binding(POST))
.build();
.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build();
Saml2PostAuthenticationRequest authenticationRequest = mock(Saml2PostAuthenticationRequest.class);
when(authenticationRequest.getAuthenticationRequestUri()).thenReturn("uri");
when(authenticationRequest.getRelayState()).thenReturn("relay");
when(authenticationRequest.getSamlRequest()).thenReturn("saml");
when(this.repository.findByRegistrationId("registration-id")).thenReturn(relyingParty);
when(this.factory.createPostAuthenticationRequest(any()))
.thenReturn(authenticationRequest);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter
(this.repository);
given(authenticationRequest.getAuthenticationRequestUri()).willReturn("uri");
given(authenticationRequest.getRelayState()).willReturn("relay");
given(authenticationRequest.getSamlRequest()).willReturn("saml");
given(this.repository.findByRegistrationId("registration-id")).willReturn(relyingParty);
given(this.factory.createPostAuthenticationRequest(any())).willReturn(authenticationRequest);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
filter.setAuthenticationRequestFactory(this.factory);
filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getContentAsString())
.contains("<form action=\"uri\" method=\"post\">")
assertThat(this.response.getContentAsString()).contains("<form action=\"uri\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\" value=\"saml\"")
.contains("<input type=\"hidden\" name=\"RelayState\" value=\"relay\"");
verify(this.factory).createPostAuthenticationRequest(any());
@@ -186,23 +165,18 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
@Test
public void doFilterWhenCustomAuthenticationRequestFactoryThenUses() throws Exception {
RelyingPartyRegistration relyingParty = this.rpBuilder
.providerDetails(c -> c.binding(POST))
.build();
.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build();
Saml2PostAuthenticationRequest authenticationRequest = mock(Saml2PostAuthenticationRequest.class);
when(authenticationRequest.getAuthenticationRequestUri()).thenReturn("uri");
when(authenticationRequest.getRelayState()).thenReturn("relay");
when(authenticationRequest.getSamlRequest()).thenReturn("saml");
when(this.resolver.resolve(this.request)).thenReturn(authenticationRequestContext()
.relyingPartyRegistration(relyingParty)
.build());
when(this.factory.createPostAuthenticationRequest(any()))
.thenReturn(authenticationRequest);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter
(this.resolver, this.factory);
given(authenticationRequest.getAuthenticationRequestUri()).willReturn("uri");
given(authenticationRequest.getRelayState()).willReturn("relay");
given(authenticationRequest.getSamlRequest()).willReturn("saml");
given(this.resolver.resolve(this.request)).willReturn(TestSaml2AuthenticationRequestContexts
.authenticationRequestContext().relyingPartyRegistration(relyingParty).build());
given(this.factory.createPostAuthenticationRequest(any())).willReturn(authenticationRequest);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver,
this.factory);
filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getContentAsString())
.contains("<form action=\"uri\" method=\"post\">")
assertThat(this.response.getContentAsString()).contains("<form action=\"uri\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\" value=\"saml\"")
.contains("<input type=\"hidden\" name=\"RelayState\" value=\"relay\"");
verify(this.factory).createPostAuthenticationRequest(any());
@@ -210,33 +184,29 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
@Test
public void setRequestMatcherWhenNullThenException() {
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter
(this.repository);
assertThatCode(() -> filter.setRedirectMatcher(null))
.isInstanceOf(IllegalArgumentException.class);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
assertThatIllegalArgumentException().isThrownBy(() -> filter.setRedirectMatcher(null));
}
@Test
public void setAuthenticationRequestFactoryWhenNullThenException() {
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
assertThatCode(() -> filter.setAuthenticationRequestFactory(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException().isThrownBy(() -> filter.setAuthenticationRequestFactory(null));
}
@Test
public void doFilterWhenRequestMatcherFailsThenSkipsFilter() throws Exception {
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter
(this.repository);
filter.setRedirectMatcher(request -> false);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
filter.setRedirectMatcher((request) -> false);
filter.doFilter(this.request, this.response, this.filterChain);
verifyNoInteractions(this.repository);
}
@Test
public void doFilterWhenRelyingPartyRegistrationNotFoundThenUnauthorized() throws Exception {
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter
(this.repository);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getStatus()).isEqualTo(401);
}
}
@@ -22,20 +22,24 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.relyingPartyRegistration;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link DefaultRelyingPartyRegistrationResolver}
*/
public class DefaultRelyingPartyRegistrationResolverTests {
private final RelyingPartyRegistration registration = relyingPartyRegistration().build();
private final RelyingPartyRegistrationRepository repository =
new InMemoryRelyingPartyRegistrationRepository(this.registration);
private final DefaultRelyingPartyRegistrationResolver resolver =
new DefaultRelyingPartyRegistrationResolver(this.repository);
private final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.build();
private final RelyingPartyRegistrationRepository repository = new InMemoryRelyingPartyRegistrationRepository(
this.registration);
private final DefaultRelyingPartyRegistrationResolver resolver = new DefaultRelyingPartyRegistrationResolver(
this.repository);
@Test
public void resolveWhenRequestContainsRegistrationIdThenResolves() {
@@ -43,8 +47,7 @@ public class DefaultRelyingPartyRegistrationResolverTests {
request.setPathInfo("/some/path/" + this.registration.getRegistrationId());
RelyingPartyRegistration registration = this.resolver.convert(request);
assertThat(registration).isNotNull();
assertThat(registration.getRegistrationId())
.isEqualTo(this.registration.getRegistrationId());
assertThat(registration.getRegistrationId()).isEqualTo(this.registration.getRegistrationId());
assertThat(registration.getEntityId())
.isEqualTo("http://localhost/saml2/service-provider-metadata/" + this.registration.getRegistrationId());
assertThat(registration.getAssertionConsumerServiceLocation())
@@ -68,7 +71,7 @@ public class DefaultRelyingPartyRegistrationResolverTests {
@Test
public void constructorWhenNullRelyingPartyRegistrationThenIllegalArgument() {
assertThatCode(() -> new DefaultRelyingPartyRegistrationResolver(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultRelyingPartyRegistrationResolver(null));
}
}
@@ -20,12 +20,12 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link DefaultSaml2AuthenticationRequestContextResolver}
@@ -36,36 +36,38 @@ import static org.springframework.security.saml2.credentials.TestSaml2X509Creden
public class DefaultSaml2AuthenticationRequestContextResolverTests {
private static final String ASSERTING_PARTY_SSO_URL = "https://idp.example.com/sso";
private static final String RELYING_PARTY_SSO_URL = "https://sp.example.com/sso";
private static final String ASSERTING_PARTY_ENTITY_ID = "asserting-party-entity-id";
private static final String RELYING_PARTY_ENTITY_ID = "relying-party-entity-id";
private static final String REGISTRATION_ID = "registration-id";
private MockHttpServletRequest request;
private RelyingPartyRegistration.Builder relyingPartyBuilder;
private Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver
= new DefaultSaml2AuthenticationRequestContextResolver(
new DefaultRelyingPartyRegistrationResolver(id -> relyingPartyBuilder.build()));
private Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver = new DefaultSaml2AuthenticationRequestContextResolver(
new DefaultRelyingPartyRegistrationResolver((id) -> this.relyingPartyBuilder.build()));
@Before
public void setup() {
this.request = new MockHttpServletRequest();
this.request.setPathInfo("/saml2/authenticate/registration-id");
this.relyingPartyBuilder = RelyingPartyRegistration
.withRegistrationId(REGISTRATION_ID)
this.relyingPartyBuilder = RelyingPartyRegistration.withRegistrationId(REGISTRATION_ID)
.localEntityIdTemplate(RELYING_PARTY_ENTITY_ID)
.providerDetails(c -> c.entityId(ASSERTING_PARTY_ENTITY_ID))
.providerDetails(c -> c.webSsoUrl(ASSERTING_PARTY_SSO_URL))
.providerDetails((c) -> c.entityId(ASSERTING_PARTY_ENTITY_ID))
.providerDetails((c) -> c.webSsoUrl(ASSERTING_PARTY_SSO_URL))
.assertionConsumerServiceUrlTemplate(RELYING_PARTY_SSO_URL)
.credentials(c -> c.add(relyingPartyVerifyingCredential()));
.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()));
}
@Test
public void resolveWhenRequestAndRelyingPartyNotNullThenCreateSaml2AuthenticationRequestContext() {
this.request.addParameter("RelayState", "relay-state");
Saml2AuthenticationRequestContext context =
this.authenticationRequestContextResolver.resolve(this.request);
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(this.request);
assertThat(context).isNotNull();
assertThat(context.getAssertionConsumerServiceUrl()).isEqualTo(RELYING_PARTY_SSO_URL);
assertThat(context.getRelayState()).isEqualTo("relay-state");
@@ -77,29 +79,22 @@ public class DefaultSaml2AuthenticationRequestContextResolverTests {
@Test
public void resolveWhenAssertionConsumerServiceUrlTemplateContainsRegistrationIdThenResolves() {
this.relyingPartyBuilder
.assertionConsumerServiceLocation("/saml2/authenticate/{registrationId}");
Saml2AuthenticationRequestContext context =
this.authenticationRequestContextResolver.resolve(this.request);
this.relyingPartyBuilder.assertionConsumerServiceLocation("/saml2/authenticate/{registrationId}");
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(this.request);
assertThat(context.getAssertionConsumerServiceUrl()).isEqualTo("/saml2/authenticate/registration-id");
}
@Test
public void resolveWhenAssertionConsumerServiceUrlTemplateContainsBaseUrlThenResolves() {
this.relyingPartyBuilder
.assertionConsumerServiceLocation("{baseUrl}/saml2/authenticate/{registrationId}");
Saml2AuthenticationRequestContext context =
this.authenticationRequestContextResolver.resolve(this.request);
this.relyingPartyBuilder.assertionConsumerServiceLocation("{baseUrl}/saml2/authenticate/{registrationId}");
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(this.request);
assertThat(context.getAssertionConsumerServiceUrl())
.isEqualTo("http://localhost/saml2/authenticate/registration-id");
}
@Test
public void resolveWhenRelyingPartyNullThenException() {
assertThatCode(() ->
this.authenticationRequestContextResolver.resolve(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException().isThrownBy(() -> this.authenticationRequestContextResolver.resolve(null));
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.web;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
@@ -31,63 +32,63 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.UriUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.relyingPartyRegistration;
import static org.mockito.BDDMockito.given;
@RunWith(MockitoJUnitRunner.class)
public class Saml2AuthenticationTokenConverterTests {
@Mock
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver;
RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistration().build();
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.build();
@Test
public void convertWhenSamlResponseThenToken() {
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter
(this.relyingPartyRegistrationResolver);
when(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.thenReturn(this.relyingPartyRegistration);
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
this.relyingPartyRegistrationResolver);
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.willReturn(this.relyingPartyRegistration);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("SAMLResponse", Saml2Utils.samlEncode("response".getBytes(UTF_8)));
request.setParameter("SAMLResponse", Saml2Utils.samlEncode("response".getBytes(StandardCharsets.UTF_8)));
Saml2AuthenticationToken token = converter.convert(request);
assertThat(token.getSaml2Response()).isEqualTo("response");
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
.isEqualTo(relyingPartyRegistration.getRegistrationId());
.isEqualTo(this.relyingPartyRegistration.getRegistrationId());
}
@Test
public void convertWhenNoSamlResponseThenNull() {
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter
(this.relyingPartyRegistrationResolver);
when(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.thenReturn(this.relyingPartyRegistration);
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
this.relyingPartyRegistrationResolver);
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.willReturn(this.relyingPartyRegistration);
MockHttpServletRequest request = new MockHttpServletRequest();
assertThat(converter.convert(request)).isNull();
}
@Test
public void convertWhenNoRelyingPartyRegistrationThenNull() {
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter
(this.relyingPartyRegistrationResolver);
when(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.thenReturn(null);
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
this.relyingPartyRegistrationResolver);
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class))).willReturn(null);
MockHttpServletRequest request = new MockHttpServletRequest();
assertThat(converter.convert(request)).isNull();
}
@Test
public void convertWhenGetRequestThenInflates() {
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter
(this.relyingPartyRegistrationResolver);
when(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.thenReturn(this.relyingPartyRegistration);
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
this.relyingPartyRegistrationResolver);
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.willReturn(this.relyingPartyRegistration);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
byte[] deflated = Saml2Utils.samlDeflate("response");
@@ -96,21 +97,20 @@ public class Saml2AuthenticationTokenConverterTests {
Saml2AuthenticationToken token = converter.convert(request);
assertThat(token.getSaml2Response()).isEqualTo("response");
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
.isEqualTo(relyingPartyRegistration.getRegistrationId());
.isEqualTo(this.relyingPartyRegistration.getRegistrationId());
}
@Test
public void constructorWhenResolverIsNullThenIllegalArgument() {
assertThatCode(() -> new Saml2AuthenticationTokenConverter(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException().isThrownBy(() -> new Saml2AuthenticationTokenConverter(null));
}
@Test
public void convertWhenUsingSamlUtilsBase64ThenXmlIsValid() throws Exception {
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter
(this.relyingPartyRegistrationResolver);
when(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.thenReturn(this.relyingPartyRegistration);
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
this.relyingPartyRegistrationResolver);
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
.willReturn(this.relyingPartyRegistration);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("SAMLResponse", getSsoCircleEncodedXml());
Saml2AuthenticationToken token = converter.convert(request);
@@ -118,8 +118,7 @@ public class Saml2AuthenticationTokenConverterTests {
}
private void validateSsoCircleXml(String xml) {
assertThat(xml)
.contains("InResponseTo=\"ARQ9a73ead-7dcf-45a8-89eb-26f3c9900c36\"")
assertThat(xml).contains("InResponseTo=\"ARQ9a73ead-7dcf-45a8-89eb-26f3c9900c36\"")
.contains(" ID=\"s246d157446618e90e43fb79bdd4d9e9e19cf2c7c4\"")
.contains("<saml:Issuer>https://idp.ssocircle.com</saml:Issuer>");
}
@@ -127,6 +126,7 @@ public class Saml2AuthenticationTokenConverterTests {
private String getSsoCircleEncodedXml() throws IOException {
ClassPathResource resource = new ClassPathResource("saml2-response-sso-circle.encoded");
String response = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
return UriUtils.decode(response, UTF_8);
return UriUtils.decode(response, StandardCharsets.UTF_8);
}
}
@@ -16,44 +16,50 @@
package org.springframework.security.saml2.provider.service.web;
import javax.servlet.FilterChain;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.security.saml2.core.TestSaml2X509Credentials.relyingPartyVerifyingCredential;
import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.noCredentials;
/**
* Tests for {@link Saml2MetadataFilter}
*/
public class Saml2MetadataFilterTest {
public class Saml2MetadataFilterTests {
RelyingPartyRegistrationRepository repository;
Saml2MetadataResolver resolver;
Saml2MetadataFilter filter;
MockHttpServletRequest request;
MockHttpServletResponse response;
FilterChain chain;
@Before
public void setup() {
this.repository = mock(RelyingPartyRegistrationRepository.class);
this.resolver = mock(Saml2MetadataResolver.class);
this.filter = new Saml2MetadataFilter(
new DefaultRelyingPartyRegistrationResolver(this.repository), this.resolver);
this.filter = new Saml2MetadataFilter(new DefaultRelyingPartyRegistrationResolver(this.repository),
this.resolver);
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.chain = mock(FilterChain.class);
@@ -61,61 +67,39 @@ public class Saml2MetadataFilterTest {
@Test
public void doFilterWhenMatcherSucceedsThenResolverInvoked() throws Exception {
// given
this.request.setPathInfo("/saml2/service-provider-metadata/registration-id");
// when
this.filter.doFilter(this.request, this.response, this.chain);
// then
verifyNoInteractions(this.chain);
verify(this.repository).findByRegistrationId("registration-id");
}
@Test
public void doFilterWhenMatcherFailsThenProcessesFilterChain() throws Exception {
// given
this.request.setPathInfo("/saml2/authenticate/registration-id");
// when
this.filter.doFilter(this.request, this.response, this.chain);
// then
verify(this.chain).doFilter(this.request, this.response);
}
@Test
public void doFilterWhenNoRelyingPartyRegistrationThenUnauthorized() throws Exception {
// given
this.request.setPathInfo("/saml2/service-provider-metadata/invalidRegistration");
when(this.repository.findByRegistrationId("invalidRegistration")).thenReturn(null);
// when
given(this.repository.findByRegistrationId("invalidRegistration")).willReturn(null);
this.filter.doFilter(this.request, this.response, this.chain);
// then
verifyNoInteractions(this.chain);
assertThat(this.response.getStatus()).isEqualTo(401);
}
@Test
public void doFilterWhenRelyingPartyRegistrationFoundThenInvokesMetadataResolver() throws Exception {
// given
this.request.setPathInfo("/saml2/service-provider-metadata/validRegistration");
RelyingPartyRegistration validRegistration = noCredentials()
.assertingPartyDetails(party -> party
.verificationX509Credentials(c -> c.add(relyingPartyVerifyingCredential())))
RelyingPartyRegistration validRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String generatedMetadata = "<xml>test</xml>";
when(this.resolver.resolve(validRegistration)).thenReturn(generatedMetadata);
this.filter = new Saml2MetadataFilter(request -> validRegistration, this.resolver);
// when
given(this.resolver.resolve(validRegistration)).willReturn(generatedMetadata);
this.filter = new Saml2MetadataFilter((request) -> validRegistration, this.resolver);
this.filter.doFilter(this.request, this.response, this.chain);
// then
verifyNoInteractions(this.chain);
assertThat(this.response.getStatus()).isEqualTo(200);
assertThat(this.response.getContentAsString()).isEqualTo(generatedMetadata);
@@ -124,21 +108,16 @@ public class Saml2MetadataFilterTest {
@Test
public void doFilterWhenCustomRequestMatcherThenUses() throws Exception {
// given
this.request.setPathInfo("/path");
this.filter.setRequestMatcher(new AntPathRequestMatcher("/path"));
// when
this.filter.doFilter(this.request, this.response, this.chain);
// then
verifyNoInteractions(this.chain);
verify(this.repository).findByRegistrationId("path");
}
@Test
public void setRequestMatcherWhenNullThenIllegalArgument() {
assertThatCode(() -> this.filter.setRequestMatcher(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null));
}
}