Support RequestedAuthnContext (#31238)
* Support RequestedAuthnContext This implements limited support for RequestedAuthnContext by : - Allowing SP administrators to define a list of authnContextClassRef to be included in the RequestedAuthnContext of a SAML Authn Request - Veirifying that the authnContext in the incoming SAML Asertion's AuthnStatement contains one of the requested authnContextClassRef - Only EXACT comparison is supported as the semantics of validating the incoming authnContextClassRef are deployment dependant and require pre-established rules for MINIMUM, MAXIMUM and BETTER Also adds necessary AuthnStatement validation as indicated by [1] and [2] [1] https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf 3.4.1.4, line 2250-2253 [2] https://kantarainitiative.github.io/SAMLprofiles/saml2int.html [SDP-IDP10]
This commit is contained in:
parent
113c1916ee
commit
b2e48c9fa7
|
@ -62,7 +62,8 @@ public class SamlRealmSettings {
|
||||||
Setting.simpleString("signing.keystore.alias", Setting.Property.NodeScope);
|
Setting.simpleString("signing.keystore.alias", Setting.Property.NodeScope);
|
||||||
public static final Setting<List<String>> SIGNING_MESSAGE_TYPES = Setting.listSetting("signing.saml_messages",
|
public static final Setting<List<String>> SIGNING_MESSAGE_TYPES = Setting.listSetting("signing.saml_messages",
|
||||||
Collections.singletonList("*"), Function.identity(), Setting.Property.NodeScope);
|
Collections.singletonList("*"), Function.identity(), Setting.Property.NodeScope);
|
||||||
|
public static final Setting<List<String>> REQUESTED_AUTHN_CONTEXT_CLASS_REF = Setting.listSetting("req_authn_context_class_ref",
|
||||||
|
Collections.emptyList(), Function.identity(),Setting.Property.NodeScope);
|
||||||
public static final Setting<TimeValue> CLOCK_SKEW = Setting.positiveTimeSetting("allowed_clock_skew", TimeValue.timeValueMinutes(3),
|
public static final Setting<TimeValue> CLOCK_SKEW = Setting.positiveTimeSetting("allowed_clock_skew", TimeValue.timeValueMinutes(3),
|
||||||
Setting.Property.NodeScope);
|
Setting.Property.NodeScope);
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ public class SamlRealmSettings {
|
||||||
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
|
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
|
||||||
NAMEID_FORMAT, NAMEID_ALLOW_CREATE, NAMEID_SP_QUALIFIER, FORCE_AUTHN,
|
NAMEID_FORMAT, NAMEID_ALLOW_CREATE, NAMEID_SP_QUALIFIER, FORCE_AUTHN,
|
||||||
POPULATE_USER_METADATA, CLOCK_SKEW,
|
POPULATE_USER_METADATA, CLOCK_SKEW,
|
||||||
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES);
|
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES, REQUESTED_AUTHN_CONTEXT_CLASS_REF);
|
||||||
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
|
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
|
||||||
set.addAll(SIGNING_SETTINGS.getAllSettings());
|
set.addAll(SIGNING_SETTINGS.getAllSettings());
|
||||||
set.addAll(SSLConfigurationSettings.withPrefix(SSL_PREFIX).getAllSettings());
|
set.addAll(SSLConfigurationSettings.withPrefix(SSL_PREFIX).getAllSettings());
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.opensaml.saml.saml2.core.Attribute;
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||||
import org.opensaml.saml.saml2.core.Audience;
|
import org.opensaml.saml.saml2.core.Audience;
|
||||||
import org.opensaml.saml.saml2.core.AudienceRestriction;
|
import org.opensaml.saml.saml2.core.AudienceRestriction;
|
||||||
|
import org.opensaml.saml.saml2.core.AuthnStatement;
|
||||||
import org.opensaml.saml.saml2.core.Conditions;
|
import org.opensaml.saml.saml2.core.Conditions;
|
||||||
import org.opensaml.saml.saml2.core.EncryptedAssertion;
|
import org.opensaml.saml.saml2.core.EncryptedAssertion;
|
||||||
import org.opensaml.saml.saml2.core.EncryptedAttribute;
|
import org.opensaml.saml.saml2.core.EncryptedAttribute;
|
||||||
|
@ -219,6 +220,7 @@ class SamlAuthenticator extends SamlRequestHandler {
|
||||||
checkConditions(assertion.getConditions());
|
checkConditions(assertion.getConditions());
|
||||||
checkIssuer(assertion.getIssuer(), assertion);
|
checkIssuer(assertion.getIssuer(), assertion);
|
||||||
checkSubject(assertion.getSubject(), assertion, allowedSamlRequestIds);
|
checkSubject(assertion.getSubject(), assertion, allowedSamlRequestIds);
|
||||||
|
checkAuthnStatement(assertion.getAuthnStatements());
|
||||||
|
|
||||||
List<Attribute> attributes = new ArrayList<>();
|
List<Attribute> attributes = new ArrayList<>();
|
||||||
for (AttributeStatement statement : assertion.getAttributeStatements()) {
|
for (AttributeStatement statement : assertion.getAttributeStatements()) {
|
||||||
|
@ -236,6 +238,33 @@ class SamlAuthenticator extends SamlRequestHandler {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkAuthnStatement(List<AuthnStatement> authnStatements) {
|
||||||
|
if (authnStatements.size() != 1) {
|
||||||
|
throw samlException("SAML Assertion subject contains {} Authn Statements while exactly one was expected.",
|
||||||
|
authnStatements.size());
|
||||||
|
}
|
||||||
|
final AuthnStatement authnStatement = authnStatements.get(0);
|
||||||
|
// "past now" that is now - the maximum skew we will tolerate. Essentially "if our clock is 2min fast, what time is it now?"
|
||||||
|
final Instant now = now();
|
||||||
|
final Instant pastNow = now.minusMillis(maxSkewInMillis());
|
||||||
|
if (authnStatement.getSessionNotOnOrAfter() != null &&
|
||||||
|
pastNow.isBefore(toInstant(authnStatement.getSessionNotOnOrAfter())) == false) {
|
||||||
|
throw samlException("Rejecting SAML assertion's Authentication Statement because [{}] is on/after [{}]", pastNow,
|
||||||
|
authnStatement.getSessionNotOnOrAfter());
|
||||||
|
}
|
||||||
|
List<String> reqAuthnCtxClassRef = this.getSpConfiguration().getReqAuthnCtxClassRef();
|
||||||
|
if (reqAuthnCtxClassRef.isEmpty() == false) {
|
||||||
|
String authnCtxClassRefValue = null;
|
||||||
|
if (authnStatement.getAuthnContext() != null && authnStatement.getAuthnContext().getAuthnContextClassRef() != null) {
|
||||||
|
authnCtxClassRefValue = authnStatement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef();
|
||||||
|
}
|
||||||
|
if (Strings.isNullOrEmpty(authnCtxClassRefValue) || reqAuthnCtxClassRef.contains(authnCtxClassRefValue) == false) {
|
||||||
|
throw samlException("Rejecting SAML assertion as the AuthnContextClassRef [{}] is not one of the ({}) that were " +
|
||||||
|
"requested in the corresponding AuthnRequest", authnCtxClassRefValue, reqAuthnCtxClassRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Attribute decrypt(EncryptedAttribute encrypted) {
|
private Attribute decrypt(EncryptedAttribute encrypted) {
|
||||||
if (decrypter == null) {
|
if (decrypter == null) {
|
||||||
logger.info("SAML message has encrypted attribute [" + text(encrypted, 32) + "], but no encryption key has been configured");
|
logger.info("SAML message has encrypted attribute [" + text(encrypted, 32) + "], but no encryption key has been configured");
|
||||||
|
|
|
@ -7,14 +7,16 @@ package org.elasticsearch.xpack.security.authc.saml;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
|
||||||
|
import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
|
||||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||||
import org.opensaml.saml.saml2.core.NameID;
|
import org.opensaml.saml.saml2.core.NameID;
|
||||||
import org.opensaml.saml.saml2.core.NameIDPolicy;
|
import org.opensaml.saml.saml2.core.NameIDPolicy;
|
||||||
|
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
|
||||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||||
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
|
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
|
||||||
*/
|
*/
|
||||||
|
@ -55,10 +57,27 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
||||||
if (nameIdSettings != null) {
|
if (nameIdSettings != null) {
|
||||||
request.setNameIDPolicy(buildNameIDPolicy());
|
request.setNameIDPolicy(buildNameIDPolicy());
|
||||||
}
|
}
|
||||||
|
if (super.serviceProvider.getReqAuthnCtxClassRef().isEmpty() == false) {
|
||||||
|
request.setRequestedAuthnContext(buildRequestedAuthnContext());
|
||||||
|
}
|
||||||
request.setForceAuthn(forceAuthn);
|
request.setForceAuthn(forceAuthn);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RequestedAuthnContext buildRequestedAuthnContext() {
|
||||||
|
RequestedAuthnContext requestedAuthnContext = SamlUtils.buildObject(RequestedAuthnContext.class, RequestedAuthnContext
|
||||||
|
.DEFAULT_ELEMENT_NAME);
|
||||||
|
for (String authnCtxClass : super.serviceProvider.getReqAuthnCtxClassRef()) {
|
||||||
|
AuthnContextClassRef authnContextClassRef = SamlUtils.buildObject(AuthnContextClassRef.class, AuthnContextClassRef
|
||||||
|
.DEFAULT_ELEMENT_NAME);
|
||||||
|
authnContextClassRef.setAuthnContextClassRef(authnCtxClass);
|
||||||
|
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
|
||||||
|
}
|
||||||
|
// We handle only EXACT comparison
|
||||||
|
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
|
||||||
|
return requestedAuthnContext;
|
||||||
|
}
|
||||||
|
|
||||||
private NameIDPolicy buildNameIDPolicy() {
|
private NameIDPolicy buildNameIDPolicy() {
|
||||||
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
|
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
|
||||||
nameIDPolicy.setFormat(nameIdSettings.format);
|
nameIDPolicy.setFormat(nameIdSettings.format);
|
||||||
|
@ -87,5 +106,4 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
||||||
this.spNameQualifier = spNameQualifier;
|
this.spNameQualifier = spNameQualifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,7 @@ import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings
|
||||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_ENTITY_ID;
|
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_ENTITY_ID;
|
||||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_LOGOUT;
|
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_LOGOUT;
|
||||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.TYPE;
|
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.TYPE;
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.REQUESTED_AUTHN_CONTEXT_CLASS_REF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is {@link Releasable} because it uses a library that thinks timers and timer tasks
|
* This class is {@link Releasable} because it uses a library that thinks timers and timer tasks
|
||||||
|
@ -273,8 +274,9 @@ public final class SamlRealm extends Realm implements Releasable {
|
||||||
final String serviceProviderId = require(config, SP_ENTITY_ID);
|
final String serviceProviderId = require(config, SP_ENTITY_ID);
|
||||||
final String assertionConsumerServiceURL = require(config, SP_ACS);
|
final String assertionConsumerServiceURL = require(config, SP_ACS);
|
||||||
final String logoutUrl = SP_LOGOUT.get(config.settings());
|
final String logoutUrl = SP_LOGOUT.get(config.settings());
|
||||||
|
final List<String> reqAuthnCtxClassRef = REQUESTED_AUTHN_CONTEXT_CLASS_REF.get(config.settings());
|
||||||
return new SpConfiguration(serviceProviderId, assertionConsumerServiceURL,
|
return new SpConfiguration(serviceProviderId, assertionConsumerServiceURL,
|
||||||
logoutUrl, buildSigningConfiguration(config), buildEncryptionCredential(config));
|
logoutUrl, buildSigningConfiguration(config), buildEncryptionCredential(config), reqAuthnCtxClassRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,12 @@ public class SpConfiguration {
|
||||||
private final String ascUrl;
|
private final String ascUrl;
|
||||||
private final String logoutUrl;
|
private final String logoutUrl;
|
||||||
private final SigningConfiguration signingConfiguration;
|
private final SigningConfiguration signingConfiguration;
|
||||||
|
private final List<String> reqAuthnCtxClassRef;
|
||||||
private final List<X509Credential> encryptionCredentials;
|
private final List<X509Credential> encryptionCredentials;
|
||||||
|
|
||||||
public SpConfiguration(final String entityId, final String ascUrl, final String logoutUrl,
|
public SpConfiguration(final String entityId, final String ascUrl, final String logoutUrl,
|
||||||
final SigningConfiguration signingConfiguration, @Nullable final List<X509Credential> encryptionCredential) {
|
final SigningConfiguration signingConfiguration, @Nullable final List<X509Credential> encryptionCredential,
|
||||||
|
final List<String> authnCtxClassRef) {
|
||||||
this.entityId = entityId;
|
this.entityId = entityId;
|
||||||
this.ascUrl = ascUrl;
|
this.ascUrl = ascUrl;
|
||||||
this.logoutUrl = logoutUrl;
|
this.logoutUrl = logoutUrl;
|
||||||
|
@ -33,6 +35,7 @@ public class SpConfiguration {
|
||||||
} else {
|
} else {
|
||||||
this.encryptionCredentials = Collections.<X509Credential>emptyList();
|
this.encryptionCredentials = Collections.<X509Credential>emptyList();
|
||||||
}
|
}
|
||||||
|
this.reqAuthnCtxClassRef = authnCtxClassRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,4 +60,8 @@ public class SpConfiguration {
|
||||||
SigningConfiguration getSigningConfiguration() {
|
SigningConfiguration getSigningConfiguration() {
|
||||||
return signingConfiguration;
|
return signingConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> getReqAuthnCtxClassRef() {
|
||||||
|
return reqAuthnCtxClassRef;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.apache.xml.security.exceptions.XMLSecurityException;
|
||||||
import org.apache.xml.security.keys.content.X509Data;
|
import org.apache.xml.security.keys.content.X509Data;
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
import org.elasticsearch.ElasticsearchSecurityException;
|
||||||
import org.elasticsearch.common.CheckedConsumer;
|
import org.elasticsearch.common.CheckedConsumer;
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
@ -101,7 +102,9 @@ import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS;
|
import static org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS;
|
||||||
import static org.opensaml.saml.common.xml.SAMLConstants.SAML20_NS;
|
import static org.opensaml.saml.common.xml.SAMLConstants.SAML20_NS;
|
||||||
|
import static org.opensaml.saml.saml2.core.AuthnContext.KERBEROS_AUTHN_CTX;
|
||||||
import static org.opensaml.saml.saml2.core.AuthnContext.PASSWORD_AUTHN_CTX;
|
import static org.opensaml.saml.saml2.core.AuthnContext.PASSWORD_AUTHN_CTX;
|
||||||
|
import static org.opensaml.saml.saml2.core.AuthnContext.X509_AUTHN_CTX;
|
||||||
import static org.opensaml.saml.saml2.core.NameIDType.TRANSIENT;
|
import static org.opensaml.saml.saml2.core.NameIDType.TRANSIENT;
|
||||||
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_ATTRIB_NAME;
|
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_ATTRIB_NAME;
|
||||||
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_BEARER;
|
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_BEARER;
|
||||||
|
@ -175,11 +178,12 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
public void setupAuthenticator() throws Exception {
|
public void setupAuthenticator() throws Exception {
|
||||||
this.clock = new ClockMock();
|
this.clock = new ClockMock();
|
||||||
this.maxSkew = TimeValue.timeValueMinutes(1);
|
this.maxSkew = TimeValue.timeValueMinutes(1);
|
||||||
this.authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
|
this.authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair), emptyList());
|
||||||
this.requestId = randomId();
|
this.requestId = randomId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SamlAuthenticator buildAuthenticator(Supplier<List<Credential>> credentials) throws Exception {
|
private SamlAuthenticator buildAuthenticator(Supplier<List<Credential>> credentials, List<String> reqAuthnCtxClassRef) throws
|
||||||
|
Exception {
|
||||||
final Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
final Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
||||||
final Settings realmSettings = Settings.EMPTY;
|
final Settings realmSettings = Settings.EMPTY;
|
||||||
final IdpConfiguration idp = new IdpConfiguration(IDP_ENTITY_ID, credentials);
|
final IdpConfiguration idp = new IdpConfiguration(IDP_ENTITY_ID, credentials);
|
||||||
|
@ -188,7 +192,8 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
(X509Credential) buildOpenSamlCredential(spSigningCertificatePair).get(0));
|
(X509Credential) buildOpenSamlCredential(spSigningCertificatePair).get(0));
|
||||||
final List<X509Credential> spEncryptionCredentials = buildOpenSamlCredential(spEncryptionCertificatePairs).stream()
|
final List<X509Credential> spEncryptionCredentials = buildOpenSamlCredential(spEncryptionCertificatePairs).stream()
|
||||||
.map((cred) -> (X509Credential) cred).collect(Collectors.<X509Credential>toList());
|
.map((cred) -> (X509Credential) cred).collect(Collectors.<X509Credential>toList());
|
||||||
final SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, null, signingConfiguration, spEncryptionCredentials);
|
final SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, null, signingConfiguration, spEncryptionCredentials,
|
||||||
|
reqAuthnCtxClassRef);
|
||||||
final Environment env = TestEnvironment.newEnvironment(globalSettings);
|
final Environment env = TestEnvironment.newEnvironment(globalSettings);
|
||||||
return new SamlAuthenticator(
|
return new SamlAuthenticator(
|
||||||
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),
|
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),
|
||||||
|
@ -689,7 +694,143 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
assertThat(exception.getMessage(), containsString("has no Subject"));
|
assertThat(exception.getMessage(), containsString("has no Subject"));
|
||||||
assertThat(exception.getCause(), nullValue());
|
assertThat(exception.getCause(), nullValue());
|
||||||
assertThat(SamlUtils.isSamlException(exception), is(true));
|
assertThat(SamlUtils.isSamlException(exception), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAssertionWithoutAuthnStatementIsRejected() throws Exception {
|
||||||
|
Instant now = clock.instant();
|
||||||
|
Instant validUntil = now.plusSeconds(30);
|
||||||
|
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
|
||||||
|
"<proto:Response Destination='" + SP_ACS_URL + "' ID='" + randomId() + "' InResponseTo='" + requestId +
|
||||||
|
"' IssueInstant='" + now + "' Version='2.0'" +
|
||||||
|
" xmlns:proto='urn:oasis:names:tc:SAML:2.0:protocol'" +
|
||||||
|
" xmlns:assert='urn:oasis:names:tc:SAML:2.0:assertion'" +
|
||||||
|
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
|
||||||
|
" xmlns:xs='http://www.w3.org/2001/XMLSchema'" +
|
||||||
|
" xmlns:ds='http://www.w3.org/2000/09/xmldsig#' >" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<proto:Status><proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'/></proto:Status>" +
|
||||||
|
"<assert:Assertion ID='" + randomId() + "' IssueInstant='" + now + "' Version='2.0'>" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<assert:Subject>" +
|
||||||
|
"<assert:NameID SPNameQualifier='" + SP_ENTITY_ID + "' Format='" + TRANSIENT + "'>randomopaquestring</assert:NameID>" +
|
||||||
|
"<assert:SubjectConfirmation Method='" + METHOD_BEARER + "'>" +
|
||||||
|
"<assert:SubjectConfirmationData NotOnOrAfter='" + validUntil + "' Recipient='" + SP_ACS_URL + "' " +
|
||||||
|
" InResponseTo='" + requestId + "'/>" +
|
||||||
|
"</assert:SubjectConfirmation>" +
|
||||||
|
"</assert:Subject>" +
|
||||||
|
"<assert:AttributeStatement><assert:Attribute " +
|
||||||
|
" NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri' Name='urn:oid:0.9.2342.19200300.100.1.1'>" +
|
||||||
|
"<assert:AttributeValue xsi:type='xs:string'>daredevil</assert:AttributeValue>" +
|
||||||
|
"</assert:Attribute></assert:AttributeStatement>" +
|
||||||
|
"</assert:Assertion>" +
|
||||||
|
"</proto:Response>";
|
||||||
|
SamlToken token = token(signDoc(xml));
|
||||||
|
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
||||||
|
assertThat(exception.getMessage(), containsString("Authn Statements while exactly one was expected."));
|
||||||
|
assertThat(exception.getCause(), nullValue());
|
||||||
|
assertThat(SamlUtils.isSamlException(exception), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExpiredAuthnStatementSessionIsRejected() throws Exception {
|
||||||
|
Instant now = clock.instant();
|
||||||
|
Instant validUntil = now.plusSeconds(120);
|
||||||
|
Instant sessionValidUntil = now.plusSeconds(60);
|
||||||
|
final String nameId = randomAlphaOfLengthBetween(12, 24);
|
||||||
|
final String sessionindex = randomId();
|
||||||
|
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
|
||||||
|
"<proto:Response Destination='" + SP_ACS_URL + "' ID='" + randomId() + "' InResponseTo='" + requestId +
|
||||||
|
"' IssueInstant='" + now + "' Version='2.0'" +
|
||||||
|
" xmlns:proto='urn:oasis:names:tc:SAML:2.0:protocol'" +
|
||||||
|
" xmlns:assert='urn:oasis:names:tc:SAML:2.0:assertion'" +
|
||||||
|
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
|
||||||
|
" xmlns:xs='http://www.w3.org/2001/XMLSchema'" +
|
||||||
|
" xmlns:ds='http://www.w3.org/2000/09/xmldsig#' >" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<proto:Status><proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'/></proto:Status>" +
|
||||||
|
"<assert:Assertion ID='" + sessionindex + "' IssueInstant='" + now + "' Version='2.0'>" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<assert:Subject>" +
|
||||||
|
"<assert:NameID Format='" + TRANSIENT + "'>" + nameId + "</assert:NameID>" +
|
||||||
|
"<assert:SubjectConfirmation Method='" + METHOD_BEARER + "'>" +
|
||||||
|
"<assert:SubjectConfirmationData NotOnOrAfter='" + validUntil + "' Recipient='" + SP_ACS_URL + "' " +
|
||||||
|
" InResponseTo='" + requestId + "'/>" +
|
||||||
|
"</assert:SubjectConfirmation>" +
|
||||||
|
"</assert:Subject>" +
|
||||||
|
"<assert:AuthnStatement AuthnInstant='" + now + "' SessionNotOnOrAfter='" + sessionValidUntil +
|
||||||
|
"' SessionIndex='" + sessionindex + "'>" +
|
||||||
|
"<assert:AuthnContext>" +
|
||||||
|
"<assert:AuthnContextClassRef>" + PASSWORD_AUTHN_CTX + "</assert:AuthnContextClassRef>" +
|
||||||
|
"</assert:AuthnContext>" +
|
||||||
|
"</assert:AuthnStatement>" +
|
||||||
|
"<assert:AttributeStatement><assert:Attribute " +
|
||||||
|
" NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri' Name='urn:oid:0.9.2342.19200300.100.1.1'>" +
|
||||||
|
"<assert:AttributeValue xsi:type='xs:string'>daredevil</assert:AttributeValue>" +
|
||||||
|
"</assert:Attribute></assert:AttributeStatement>" +
|
||||||
|
"</assert:Assertion>" +
|
||||||
|
"</proto:Response>";
|
||||||
|
// check that the content is valid "now"
|
||||||
|
final SamlToken token = token(signDoc(xml));
|
||||||
|
assertThat(authenticator.authenticate(token), notNullValue());
|
||||||
|
|
||||||
|
// and still valid if we advance partway through the session expiry time
|
||||||
|
clock.fastForwardSeconds(30);
|
||||||
|
assertThat(authenticator.authenticate(token), notNullValue());
|
||||||
|
|
||||||
|
// and still valid if we advance past the expiry time, but allow for clock skew
|
||||||
|
clock.fastForwardSeconds((int) (30 + maxSkew.seconds() / 2));
|
||||||
|
assertThat(authenticator.authenticate(token), notNullValue());
|
||||||
|
|
||||||
|
// but fails once we get past the clock skew allowance
|
||||||
|
clock.fastForwardSeconds((int) (1 + maxSkew.seconds() / 2));
|
||||||
|
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
||||||
|
assertThat(exception.getMessage(), containsString("on/after"));
|
||||||
|
assertThat(exception.getMessage(), containsString("Authentication Statement"));
|
||||||
|
assertThat(exception.getCause(), nullValue());
|
||||||
|
assertThat(SamlUtils.isSamlException(exception), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIncorrectAuthnContextClassRefIsRejected() throws Exception {
|
||||||
|
Instant now = clock.instant();
|
||||||
|
Instant validUntil = now.plusSeconds(30);
|
||||||
|
final String nameId = randomAlphaOfLengthBetween(12, 24);
|
||||||
|
final String sessionindex = randomId();
|
||||||
|
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
|
||||||
|
"<proto:Response Destination='" + SP_ACS_URL + "' ID='" + randomId() + "' InResponseTo='" + requestId +
|
||||||
|
"' IssueInstant='" + now + "' Version='2.0'" +
|
||||||
|
" xmlns:proto='urn:oasis:names:tc:SAML:2.0:protocol'" +
|
||||||
|
" xmlns:assert='urn:oasis:names:tc:SAML:2.0:assertion'" +
|
||||||
|
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
|
||||||
|
" xmlns:xs='http://www.w3.org/2001/XMLSchema'" +
|
||||||
|
" xmlns:ds='http://www.w3.org/2000/09/xmldsig#' >" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<proto:Status><proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'/></proto:Status>" +
|
||||||
|
"<assert:Assertion ID='" + sessionindex + "' IssueInstant='" + now + "' Version='2.0'>" +
|
||||||
|
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
|
||||||
|
"<assert:Subject>" +
|
||||||
|
"<assert:NameID Format='" + TRANSIENT + "'>" + nameId + "</assert:NameID>" +
|
||||||
|
"<assert:SubjectConfirmation Method='" + METHOD_BEARER + "'>" +
|
||||||
|
"<assert:SubjectConfirmationData NotOnOrAfter='" + validUntil + "' Recipient='" + SP_ACS_URL + "' " +
|
||||||
|
" InResponseTo='" + requestId + "'/>" +
|
||||||
|
"</assert:SubjectConfirmation>" +
|
||||||
|
"</assert:Subject>" +
|
||||||
|
"<assert:AuthnStatement AuthnInstant='" + now + "' SessionNotOnOrAfter='" + validUntil +
|
||||||
|
"' SessionIndex='" + sessionindex + "'>" +
|
||||||
|
"<assert:AuthnContext>" +
|
||||||
|
"<assert:AuthnContextClassRef>" + PASSWORD_AUTHN_CTX + "</assert:AuthnContextClassRef>" +
|
||||||
|
"</assert:AuthnContext>" +
|
||||||
|
"</assert:AuthnStatement>" +
|
||||||
|
"<assert:AttributeStatement><assert:Attribute " +
|
||||||
|
" NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri' Name='urn:oid:0.9.2342.19200300.100.1.1'>" +
|
||||||
|
"<assert:AttributeValue xsi:type='xs:string'>daredevil</assert:AttributeValue>" +
|
||||||
|
"</assert:Attribute></assert:AttributeStatement>" +
|
||||||
|
"</assert:Assertion>" +
|
||||||
|
"</proto:Response>";
|
||||||
|
SamlAuthenticator authenticatorWithReqAuthnCtx = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair),
|
||||||
|
Arrays.asList(X509_AUTHN_CTX, KERBEROS_AUTHN_CTX));
|
||||||
|
SamlToken token = token(signDoc(xml));
|
||||||
|
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticatorWithReqAuthnCtx.authenticate(token));
|
||||||
|
assertThat(exception.getMessage(), containsString("Rejecting SAML assertion as the AuthnContextClassRef"));
|
||||||
|
assertThat(SamlUtils.isSamlException(exception), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAssertionWithoutSubjectConfirmationIsRejected() throws Exception {
|
public void testAssertionWithoutSubjectConfirmationIsRejected() throws Exception {
|
||||||
|
@ -1066,7 +1207,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
credentials.addAll(buildOpenSamlCredential(key));
|
credentials.addAll(buildOpenSamlCredential(key));
|
||||||
}
|
}
|
||||||
this.authenticator = buildAuthenticator(() -> credentials);
|
this.authenticator = buildAuthenticator(() -> credentials, emptyList());
|
||||||
final CryptoTransform signer = randomBoolean() ? this::signDoc : this::signAssertions;
|
final CryptoTransform signer = randomBoolean() ? this::signDoc : this::signAssertions;
|
||||||
Instant now = clock.instant();
|
Instant now = clock.instant();
|
||||||
Instant validUntil = now.plusSeconds(30);
|
Instant validUntil = now.plusSeconds(30);
|
||||||
|
@ -1634,7 +1775,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testFailureWhenIdPCredentialsAreEmpty() throws Exception {
|
public void testFailureWhenIdPCredentialsAreEmpty() throws Exception {
|
||||||
authenticator = buildAuthenticator(() -> emptyList());
|
authenticator = buildAuthenticator(() -> emptyList(), emptyList());
|
||||||
final String xml = getSimpleResponse(clock.instant());
|
final String xml = getSimpleResponse(clock.instant());
|
||||||
final SamlToken token = token(signDoc(xml));
|
final SamlToken token = token(signDoc(xml));
|
||||||
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
||||||
|
@ -1642,11 +1783,11 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
assertThat(exception.getMessage(), containsString("SAML Signature"));
|
assertThat(exception.getMessage(), containsString("SAML Signature"));
|
||||||
assertThat(exception.getMessage(), containsString("could not be validated"));
|
assertThat(exception.getMessage(), containsString("could not be validated"));
|
||||||
//Restore the authenticator with credentials for the rest of the test cases
|
//Restore the authenticator with credentials for the rest of the test cases
|
||||||
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
|
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair), emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testFailureWhenIdPCredentialsAreNull() throws Exception {
|
public void testFailureWhenIdPCredentialsAreNull() throws Exception {
|
||||||
authenticator = buildAuthenticator(() -> singletonList(null));
|
authenticator = buildAuthenticator(() -> singletonList(null), emptyList());
|
||||||
final String xml = getSimpleResponse(clock.instant());
|
final String xml = getSimpleResponse(clock.instant());
|
||||||
final SamlToken token = token(signDoc(xml));
|
final SamlToken token = token(signDoc(xml));
|
||||||
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
|
||||||
|
@ -1654,7 +1795,7 @@ public class SamlAuthenticatorTests extends SamlTestCase {
|
||||||
assertThat(exception.getMessage(), containsString("SAML Signature"));
|
assertThat(exception.getMessage(), containsString("SAML Signature"));
|
||||||
assertThat(exception.getMessage(), containsString("could not be validated"));
|
assertThat(exception.getMessage(), containsString("could not be validated"));
|
||||||
//Restore the authenticator with credentials for the rest of the test cases
|
//Restore the authenticator with credentials for the rest of the test cases
|
||||||
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair));
|
authenticator = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair), emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface CryptoTransform {
|
private interface CryptoTransform {
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
package org.elasticsearch.xpack.security.authc.saml;
|
package org.elasticsearch.xpack.security.authc.saml;
|
||||||
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -20,6 +23,8 @@ import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.opensaml.saml.saml2.core.AuthnContext.KERBEROS_AUTHN_CTX;
|
||||||
|
import static org.opensaml.saml.saml2.core.AuthnContext.SMARTCARD_AUTHN_CTX;
|
||||||
|
|
||||||
public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
||||||
|
|
||||||
|
@ -47,7 +52,7 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBuildRequestWithPersistentNameAndNoForceAuth() throws Exception {
|
public void testBuildRequestWithPersistentNameAndNoForceAuth() throws Exception {
|
||||||
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null);
|
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null, Collections.emptyList());
|
||||||
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
||||||
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||||
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||||
|
@ -68,10 +73,11 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
||||||
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.FALSE));
|
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.FALSE));
|
||||||
|
|
||||||
assertThat(request.isForceAuthn(), equalTo(Boolean.FALSE));
|
assertThat(request.isForceAuthn(), equalTo(Boolean.FALSE));
|
||||||
|
assertThat(request.getRequestedAuthnContext(), equalTo(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBuildRequestWithTransientNameAndForceAuthTrue() throws Exception {
|
public void testBuildRequestWithTransientNameAndForceAuthTrue() throws Exception {
|
||||||
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null);
|
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null, Collections.emptyList());
|
||||||
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
||||||
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||||
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||||
|
@ -94,6 +100,68 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
||||||
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.TRUE));
|
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.TRUE));
|
||||||
|
|
||||||
assertThat(request.isForceAuthn(), equalTo(Boolean.TRUE));
|
assertThat(request.isForceAuthn(), equalTo(Boolean.TRUE));
|
||||||
|
assertThat(request.getRequestedAuthnContext(), equalTo(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBuildRequestWithRequestedAuthnContext() throws Exception {
|
||||||
|
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null,
|
||||||
|
Collections.singletonList(KERBEROS_AUTHN_CTX));
|
||||||
|
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
||||||
|
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||||
|
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||||
|
Clock.systemUTC());
|
||||||
|
builder.nameIDPolicy(new SamlAuthnRequestBuilder.NameIDPolicySettings(NameID.PERSISTENT, false, SP_ENTITY_ID));
|
||||||
|
builder.forceAuthn(null);
|
||||||
|
|
||||||
|
final AuthnRequest request = buildAndValidateAuthnRequest(builder);
|
||||||
|
|
||||||
|
assertThat(request.getIssuer().getValue(), equalTo(SP_ENTITY_ID));
|
||||||
|
assertThat(request.getProtocolBinding(), equalTo(SAMLConstants.SAML2_POST_BINDING_URI));
|
||||||
|
|
||||||
|
assertThat(request.getAssertionConsumerServiceURL(), equalTo(ACS_URL));
|
||||||
|
|
||||||
|
assertThat(request.getNameIDPolicy(), notNullValue());
|
||||||
|
assertThat(request.getNameIDPolicy().getFormat(), equalTo(NameID.PERSISTENT));
|
||||||
|
assertThat(request.getNameIDPolicy().getSPNameQualifier(), equalTo(SP_ENTITY_ID));
|
||||||
|
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.FALSE));
|
||||||
|
|
||||||
|
assertThat(request.isForceAuthn(), equalTo(Boolean.FALSE));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().size(), equalTo(1));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().get(0).getAuthnContextClassRef(),
|
||||||
|
equalTo(KERBEROS_AUTHN_CTX));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBuildRequestWithRequestedAuthnContexts() throws Exception {
|
||||||
|
List<String> reqAuthnCtxClassRef = Arrays.asList(KERBEROS_AUTHN_CTX,
|
||||||
|
SMARTCARD_AUTHN_CTX,
|
||||||
|
"http://an.arbitrary/mfa-profile");
|
||||||
|
SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, null, null, null, reqAuthnCtxClassRef);
|
||||||
|
final SamlAuthnRequestBuilder builder = new SamlAuthnRequestBuilder(
|
||||||
|
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||||
|
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||||
|
Clock.systemUTC());
|
||||||
|
builder.nameIDPolicy(new SamlAuthnRequestBuilder.NameIDPolicySettings(NameID.PERSISTENT, false, SP_ENTITY_ID));
|
||||||
|
builder.forceAuthn(null);
|
||||||
|
final AuthnRequest request = buildAndValidateAuthnRequest(builder);
|
||||||
|
|
||||||
|
assertThat(request.getIssuer().getValue(), equalTo(SP_ENTITY_ID));
|
||||||
|
assertThat(request.getProtocolBinding(), equalTo(SAMLConstants.SAML2_POST_BINDING_URI));
|
||||||
|
|
||||||
|
assertThat(request.getAssertionConsumerServiceURL(), equalTo(ACS_URL));
|
||||||
|
|
||||||
|
assertThat(request.getNameIDPolicy(), notNullValue());
|
||||||
|
assertThat(request.getNameIDPolicy().getFormat(), equalTo(NameID.PERSISTENT));
|
||||||
|
assertThat(request.getNameIDPolicy().getSPNameQualifier(), equalTo(SP_ENTITY_ID));
|
||||||
|
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.FALSE));
|
||||||
|
|
||||||
|
assertThat(request.isForceAuthn(), equalTo(Boolean.FALSE));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().size(), equalTo(3));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().get(0).getAuthnContextClassRef(),
|
||||||
|
equalTo(KERBEROS_AUTHN_CTX));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().get(1).getAuthnContextClassRef(),
|
||||||
|
equalTo(SMARTCARD_AUTHN_CTX));
|
||||||
|
assertThat(request.getRequestedAuthnContext().getAuthnContextClassRefs().get(2).getAuthnContextClassRef(),
|
||||||
|
equalTo("http://an.arbitrary/mfa-profile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthnRequest buildAndValidateAuthnRequest(SamlAuthnRequestBuilder builder) {
|
private AuthnRequest buildAndValidateAuthnRequest(SamlAuthnRequestBuilder builder) {
|
||||||
|
|
|
@ -213,7 +213,7 @@ public class SamlLogoutRequestHandlerTests extends SamlTestCase {
|
||||||
final X509Credential spCredential = (X509Credential) buildOpenSamlCredential(readRandomKeyPair()).get(0);
|
final X509Credential spCredential = (X509Credential) buildOpenSamlCredential(readRandomKeyPair()).get(0);
|
||||||
final SigningConfiguration signingConfiguration = new SigningConfiguration(Collections.singleton("*"), spCredential);
|
final SigningConfiguration signingConfiguration = new SigningConfiguration(Collections.singleton("*"), spCredential);
|
||||||
final SpConfiguration sp = new SpConfiguration("https://sp.test/", "https://sp.test/saml/asc", LOGOUT_URL,
|
final SpConfiguration sp = new SpConfiguration("https://sp.test/", "https://sp.test/saml/asc", LOGOUT_URL,
|
||||||
signingConfiguration, Arrays.asList(spCredential));
|
signingConfiguration, Arrays.asList(spCredential), Collections.emptyList());
|
||||||
final Environment env = TestEnvironment.newEnvironment(globalSettings);
|
final Environment env = TestEnvironment.newEnvironment(globalSettings);
|
||||||
return new SamlLogoutRequestHandler(
|
return new SamlLogoutRequestHandler(
|
||||||
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),
|
new RealmConfig("saml_test", realmSettings, globalSettings, env, new ThreadContext(globalSettings)),
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authc.saml;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -38,7 +39,7 @@ public class SamlLogoutRequestMessageBuilderTests extends SamlTestCase {
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
SamlUtils.initialize(logger);
|
SamlUtils.initialize(logger);
|
||||||
sp = new SpConfiguration(SP_ENTITY_ID, "http://sp.example.com/saml/acs", null, null, null);
|
sp = new SpConfiguration(SP_ENTITY_ID, "http://sp.example.com/saml/acs", null, null, null, Collections.emptyList());
|
||||||
idpRole = SamlUtils.buildObject(IDPSSODescriptor.class, IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
|
idpRole = SamlUtils.buildObject(IDPSSODescriptor.class, IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
|
||||||
idp = SamlUtils.buildObject(EntityDescriptor.class, EntityDescriptor.DEFAULT_ELEMENT_NAME);
|
idp = SamlUtils.buildObject(EntityDescriptor.class, EntityDescriptor.DEFAULT_ELEMENT_NAME);
|
||||||
idp.setEntityID(IDP_ENTITY_ID);
|
idp.setEntityID(IDP_ENTITY_ID);
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class SamlRealmTestHelper {
|
||||||
slo.setLocation(IDP_LOGOUT_URL);
|
slo.setLocation(IDP_LOGOUT_URL);
|
||||||
|
|
||||||
final SpConfiguration spConfiguration = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, SP_LOGOUT_URL,
|
final SpConfiguration spConfiguration = new SpConfiguration(SP_ENTITY_ID, SP_ACS_URL, SP_LOGOUT_URL,
|
||||||
new SigningConfiguration(Collections.singleton("*"), credential), Arrays.asList(credential));
|
new SigningConfiguration(Collections.singleton("*"), credential), Arrays.asList(credential), Collections.emptyList());
|
||||||
return new SamlRealm(realmConfig, mock(UserRoleMapper.class), mock(SamlAuthenticator.class),
|
return new SamlRealm(realmConfig, mock(UserRoleMapper.class), mock(SamlAuthenticator.class),
|
||||||
mock(SamlLogoutRequestHandler.class), () -> idpDescriptor, spConfiguration);
|
mock(SamlLogoutRequestHandler.class), () -> idpDescriptor, spConfiguration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ public class SamlRealmTests extends SamlTestCase {
|
||||||
final Boolean populateUserMetadata = randomFrom(Boolean.TRUE, Boolean.FALSE, null);
|
final Boolean populateUserMetadata = randomFrom(Boolean.TRUE, Boolean.FALSE, null);
|
||||||
final UserRoleMapper roleMapper = mock(UserRoleMapper.class);
|
final UserRoleMapper roleMapper = mock(UserRoleMapper.class);
|
||||||
final EntityDescriptor idp = mockIdp();
|
final EntityDescriptor idp = mockIdp();
|
||||||
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null);
|
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
|
||||||
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
||||||
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ public class SamlRealmTests extends SamlTestCase {
|
||||||
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
||||||
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
||||||
final EntityDescriptor idp = mockIdp();
|
final EntityDescriptor idp = mockIdp();
|
||||||
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null);
|
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
|
||||||
|
|
||||||
final SettingsException settingsException = expectThrows(SettingsException.class,
|
final SettingsException settingsException = expectThrows(SettingsException.class,
|
||||||
() -> new SamlRealm(config, roleMapper, authenticator, logoutHandler, () -> idp, sp));
|
() -> new SamlRealm(config, roleMapper, authenticator, logoutHandler, () -> idp, sp));
|
||||||
|
@ -256,7 +256,7 @@ public class SamlRealmTests extends SamlTestCase {
|
||||||
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
||||||
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
||||||
final EntityDescriptor idp = mockIdp();
|
final EntityDescriptor idp = mockIdp();
|
||||||
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null);
|
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
|
||||||
|
|
||||||
final SettingsException settingsException = expectThrows(SettingsException.class,
|
final SettingsException settingsException = expectThrows(SettingsException.class,
|
||||||
() -> new SamlRealm(config, roleMapper, authenticator, logoutHandler, () -> idp, sp));
|
() -> new SamlRealm(config, roleMapper, authenticator, logoutHandler, () -> idp, sp));
|
||||||
|
@ -266,7 +266,7 @@ public class SamlRealmTests extends SamlTestCase {
|
||||||
public void testNonMatchingPrincipalPatternThrowsSamlException() throws Exception {
|
public void testNonMatchingPrincipalPatternThrowsSamlException() throws Exception {
|
||||||
final UserRoleMapper roleMapper = mock(UserRoleMapper.class);
|
final UserRoleMapper roleMapper = mock(UserRoleMapper.class);
|
||||||
final EntityDescriptor idp = mockIdp();
|
final EntityDescriptor idp = mockIdp();
|
||||||
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null);
|
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
|
||||||
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
||||||
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
||||||
|
|
||||||
|
@ -516,7 +516,7 @@ public class SamlRealmTests extends SamlTestCase {
|
||||||
slo.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
|
slo.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
|
||||||
slo.setLocation("https://logout.saml/");
|
slo.setLocation("https://logout.saml/");
|
||||||
|
|
||||||
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null);
|
final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
|
||||||
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
final SamlAuthenticator authenticator = mock(SamlAuthenticator.class);
|
||||||
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
final SamlLogoutRequestHandler logoutHandler = mock(SamlLogoutRequestHandler.class);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue