Merge Formatting Changes
Issue gh-8945
This commit is contained in:
+66
-56
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-8
@@ -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() : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+38
-34
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
+8
-13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+49
-49
@@ -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,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+62
-48
@@ -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,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-28
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+237
-252
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+61
-99
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-10
@@ -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");
|
||||
|
||||
+45
-45
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
-45
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-28
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+60
-62
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+31
-34
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-8
@@ -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() : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+38
-34
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
+17
-27
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+22
-32
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+15
-13
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-24
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+9
-13
@@ -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
|
||||
|
||||
+71
-53
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+305
-270
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -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}
|
||||
*/
|
||||
|
||||
+18
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-8
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+75
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+25
-25
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
-80
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+26
-35
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-16
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+19
-22
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-9
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-12
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-17
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
-79
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+100
-106
@@ -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-----");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+66
-69
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+100
-106
@@ -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-----");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-14
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+207
-195
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+72
-85
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-23
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+52
-68
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-6
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-31
@@ -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\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+54
-70
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+24
-46
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+62
-97
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+24
-32
@@ -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())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-26
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
-107
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-11
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
-26
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-33
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-44
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user