KEYCLOAK-18315 SAML Client - Add parameter to request specific AttributeConsumingServiceIndex
This commit is contained in:
parent
9dc7300178
commit
ae98d8ea28
6 changed files with 142 additions and 2 deletions
|
@ -75,6 +75,11 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder attributeConsumingServiceIndex(Integer attributeConsumingServiceIndex) {
|
||||||
|
this.authnRequestType.setAttributeConsumingServiceIndex(attributeConsumingServiceIndex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SAML2AuthnRequestBuilder forceAuthn(boolean forceAuthn) {
|
public SAML2AuthnRequestBuilder forceAuthn(boolean forceAuthn) {
|
||||||
this.authnRequestType.setForceAuthn(forceAuthn);
|
this.authnRequestType.setForceAuthn(forceAuthn);
|
||||||
return this;
|
return this;
|
||||||
|
@ -85,8 +90,8 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2AuthnRequestBuilder nameIdPolicy(SAML2NameIDPolicyBuilder nameIDPolicy) {
|
public SAML2AuthnRequestBuilder nameIdPolicy(SAML2NameIDPolicyBuilder nameIDPolicyBuilder) {
|
||||||
this.authnRequestType.setNameIDPolicy(nameIDPolicy.build());
|
this.authnRequestType.setNameIDPolicy(nameIDPolicyBuilder.build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
for (String authnContextDeclRef : getAuthnContextDeclRefUris())
|
for (String authnContextDeclRef : getAuthnContextDeclRefUris())
|
||||||
requestedAuthnContext.addAuthnContextDeclRef(authnContextDeclRef);
|
requestedAuthnContext.addAuthnContextDeclRef(authnContextDeclRef);
|
||||||
|
|
||||||
|
Integer attributeConsumingServiceIndex = getConfig().getAttributeConsumingServiceIndex();
|
||||||
|
|
||||||
String loginHint = getConfig().isLoginHint() ? request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM) : null;
|
String loginHint = getConfig().isLoginHint() ? request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM) : null;
|
||||||
Boolean allowCreate = null;
|
Boolean allowCreate = null;
|
||||||
if (getConfig().getConfig().get(SAMLIdentityProviderConfig.ALLOW_CREATE) == null || getConfig().isAllowCreate())
|
if (getConfig().getConfig().get(SAMLIdentityProviderConfig.ALLOW_CREATE) == null || getConfig().isAllowCreate())
|
||||||
|
@ -143,6 +145,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
.nameIdPolicy(SAML2NameIDPolicyBuilder
|
.nameIdPolicy(SAML2NameIDPolicyBuilder
|
||||||
.format(nameIDPolicyFormat)
|
.format(nameIDPolicyFormat)
|
||||||
.setAllowCreate(allowCreate))
|
.setAllowCreate(allowCreate))
|
||||||
|
.attributeConsumingServiceIndex(attributeConsumingServiceIndex)
|
||||||
.requestedAuthnContext(requestedAuthnContext)
|
.requestedAuthnContext(requestedAuthnContext)
|
||||||
.subject(loginHint);
|
.subject(loginHint);
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
public static final String AUTHN_CONTEXT_DECL_REFS = "authnContextDeclRefs";
|
public static final String AUTHN_CONTEXT_DECL_REFS = "authnContextDeclRefs";
|
||||||
public static final String SIGN_SP_METADATA = "signSpMetadata";
|
public static final String SIGN_SP_METADATA = "signSpMetadata";
|
||||||
public static final String ALLOW_CREATE = "allowCreate";
|
public static final String ALLOW_CREATE = "allowCreate";
|
||||||
|
public static final String ATTRIBUTE_CONSUMING_SERVICE_INDEX = "attributeConsumingServiceIndex";
|
||||||
|
|
||||||
public SAMLIdentityProviderConfig() {
|
public SAMLIdentityProviderConfig() {
|
||||||
}
|
}
|
||||||
|
@ -345,6 +346,30 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
getConfig().put(ALLOW_CREATE, String.valueOf(allowCreate));
|
getConfig().put(ALLOW_CREATE, String.valueOf(allowCreate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getAttributeConsumingServiceIndex() {
|
||||||
|
Integer result = null;
|
||||||
|
String strAttributeConsumingServiceIndex = getConfig().get(ATTRIBUTE_CONSUMING_SERVICE_INDEX);
|
||||||
|
if (strAttributeConsumingServiceIndex != null && !strAttributeConsumingServiceIndex.isEmpty()) {
|
||||||
|
try {
|
||||||
|
result = Integer.parseInt(strAttributeConsumingServiceIndex);
|
||||||
|
if (result < 0) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// ignore it and use null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributeConsumingServiceIndex(Integer attributeConsumingServiceIndex) {
|
||||||
|
if (attributeConsumingServiceIndex == null || attributeConsumingServiceIndex < 0) {
|
||||||
|
getConfig().remove(ATTRIBUTE_CONSUMING_SERVICE_INDEX);
|
||||||
|
} else {
|
||||||
|
getConfig().put(ATTRIBUTE_CONSUMING_SERVICE_INDEX, String.valueOf(attributeConsumingServiceIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(RealmModel realm) {
|
public void validate(RealmModel realm) {
|
||||||
SslRequired sslRequired = realm.getSslRequired();
|
SslRequired sslRequired = realm.getSslRequired();
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
|
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
||||||
|
import org.keycloak.testsuite.saml.AbstractSamlTest;
|
||||||
|
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
|
import org.keycloak.testsuite.util.SamlClient.Binding;
|
||||||
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final class as it's not intended to be overriden.
|
||||||
|
*/
|
||||||
|
public final class KcSamlAttributeConsumingServiceIndexTest extends AbstractBrokerTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return KcSamlBrokerConfiguration.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributeConsumingServiceIndexNotSet() throws Exception {
|
||||||
|
// No Attribute Consuming Service Index set -> No attribute added to AuthnRequest
|
||||||
|
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
||||||
|
.update())
|
||||||
|
{
|
||||||
|
// Build the login request document
|
||||||
|
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null);
|
||||||
|
Document doc = SAML2Request.convert(loginRep);
|
||||||
|
new SamlClientBuilder()
|
||||||
|
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, Binding.POST)
|
||||||
|
.build() // Request to consumer IdP
|
||||||
|
.login().idp(bc.getIDPAlias()).build()
|
||||||
|
.processSamlResponse(Binding.POST) // AuthnRequest to producer IdP
|
||||||
|
.targetAttributeSamlRequest()
|
||||||
|
.transformDocument((document) -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.infof("Document: %s", DocumentUtil.asString(document));
|
||||||
|
|
||||||
|
// Find the AuthnRequest AttributeConsumingServiceIndex attribute
|
||||||
|
Node attrNode = document.getDocumentElement().getAttributes().getNamedItem("AttributeConsumingServiceIndex");
|
||||||
|
Assert.assertEquals("Unexpected AttributeConsumingServiceIndex attribute value", null, attrNode);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributeConsumingServiceIndexSet() throws Exception {
|
||||||
|
// Attribute Consuming Service Index set -> Attribute added to AuthnRequest
|
||||||
|
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
||||||
|
.setAttribute(SAMLIdentityProviderConfig.ATTRIBUTE_CONSUMING_SERVICE_INDEX, "15")
|
||||||
|
.update())
|
||||||
|
{
|
||||||
|
// Build the login request document
|
||||||
|
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null);
|
||||||
|
Document doc = SAML2Request.convert(loginRep);
|
||||||
|
new SamlClientBuilder()
|
||||||
|
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, Binding.POST)
|
||||||
|
.build() // Request to consumer IdP
|
||||||
|
.login().idp(bc.getIDPAlias()).build()
|
||||||
|
.processSamlResponse(Binding.POST) // AuthnRequest to producer IdP
|
||||||
|
.targetAttributeSamlRequest()
|
||||||
|
.transformDocument((document) -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.infof("Document: %s", DocumentUtil.asString(document));
|
||||||
|
|
||||||
|
// Find the AuthnRequest AttributeConsumingServiceIndex attribute
|
||||||
|
String attrValue = document.getDocumentElement().getAttributes().getNamedItem("AttributeConsumingServiceIndex").getNodeValue();
|
||||||
|
Assert.assertEquals("Unexpected AttributeConsumingServiceIndex attribute value", "15", attrValue);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -718,6 +718,8 @@ identity-provider.saml.entity-id=Service Provider Entity ID
|
||||||
identity-provider.saml.entity-id.tooltip=The Entity ID that will be used to uniquely identify this SAML Service Provider
|
identity-provider.saml.entity-id.tooltip=The Entity ID that will be used to uniquely identify this SAML Service Provider
|
||||||
identity-provider.saml.protocol-endpoints.saml=SAML 2.0 Service Provider Metadata
|
identity-provider.saml.protocol-endpoints.saml=SAML 2.0 Service Provider Metadata
|
||||||
identity-provider.saml.protocol-endpoints.saml.tooltip=Shows the configuration of the Service Provider endpoint
|
identity-provider.saml.protocol-endpoints.saml.tooltip=Shows the configuration of the Service Provider endpoint
|
||||||
|
identity-provider.saml.attribute-consuming-service-index=Attribute Consuming Service Index
|
||||||
|
identity-provider.saml.attribute-consuming-service-index.tooltip=Index of the Attribute Consuming Service profile to request during authentication
|
||||||
saml-config=SAML Config
|
saml-config=SAML Config
|
||||||
identity-provider.saml-config.tooltip=SAML SP and external IDP configuration.
|
identity-provider.saml-config.tooltip=SAML SP and external IDP configuration.
|
||||||
single-signon-service-url=Single Sign-On Service URL
|
single-signon-service-url=Single Sign-On Service URL
|
||||||
|
|
|
@ -302,6 +302,13 @@
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="attributeConsumingServiceIndex">{{:: 'identity-provider.saml.attribute-consuming-service-index' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" string-to-number type="number" min="0" max="65535" step="1" ng-model="identityProvider.config.attributeConsumingServiceIndex" id="attributeConsumingServiceIndex"/>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'identity-provider.saml.attribute-consuming-service-index.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend collapsed><span class="text">{{:: 'identity-provider.saml.requested-authncontext' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml.requested-authncontext.tooltip' | translate}}</kc-tooltip></legend>
|
<legend collapsed><span class="text">{{:: 'identity-provider.saml.requested-authncontext' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml.requested-authncontext.tooltip' | translate}}</kc-tooltip></legend>
|
||||||
|
|
Loading…
Reference in a new issue