KEYCLOAK-14961 SAML Client: Add ability to request specific AuthnContexts to remote IdPs

This commit is contained in:
Luca Leonardo Scorcia 2020-07-25 09:50:33 -04:00 committed by Hynek Mlnařík
parent 1c4a2db8e1
commit 67b2d5ffdd
9 changed files with 505 additions and 3 deletions

View file

@ -20,6 +20,7 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.dom.saml.v2.protocol.RequestedAuthnContextType;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
@ -108,6 +109,17 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil
return subject;
}
public SAML2AuthnRequestBuilder requestedAuthnContext(SAML2RequestedAuthnContextBuilder requestedAuthnContextBuilder) {
RequestedAuthnContextType requestedAuthnContext = requestedAuthnContextBuilder.build();
// Only emit the RequestedAuthnContext element if at least a ClassRef or a DeclRef is present
if (!requestedAuthnContext.getAuthnContextClassRef().isEmpty() ||
!requestedAuthnContext.getAuthnContextDeclRef().isEmpty())
this.authnRequestType.setRequestedAuthnContext(requestedAuthnContext);
return this;
}
public Document toDocument() {
try {
AuthnRequestType authnRequestType = createAuthnRequest();

View file

@ -0,0 +1,66 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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
*
* http://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.keycloak.saml;
import org.keycloak.dom.saml.v2.protocol.AuthnContextComparisonType;
import org.keycloak.dom.saml.v2.protocol.RequestedAuthnContextType;
import java.util.LinkedList;
import java.util.List;
public class SAML2RequestedAuthnContextBuilder {
private final RequestedAuthnContextType requestedAuthnContextType;
private AuthnContextComparisonType comparison;
private List<String> requestedAuthnContextClassRefList;
private List<String> requestedAuthnContextDeclRefList;
public SAML2RequestedAuthnContextBuilder() {
this.requestedAuthnContextType = new RequestedAuthnContextType();
this.requestedAuthnContextClassRefList = new LinkedList<String>();
this.requestedAuthnContextDeclRefList = new LinkedList<String>();
}
public SAML2RequestedAuthnContextBuilder setComparison(AuthnContextComparisonType comparison) {
this.comparison = comparison;
return this;
}
public SAML2RequestedAuthnContextBuilder addAuthnContextClassRef(String authnContextClassRef) {
this.requestedAuthnContextClassRefList.add(authnContextClassRef);
return this;
}
public SAML2RequestedAuthnContextBuilder addAuthnContextDeclRef(String authnContextDeclRef) {
this.requestedAuthnContextDeclRefList.add(authnContextDeclRef);
return this;
}
public RequestedAuthnContextType build() {
if (this.comparison != null)
this.requestedAuthnContextType.setComparison(this.comparison);
for (String requestedAuthnContextClassRef: this.requestedAuthnContextClassRefList)
if (requestedAuthnContextClassRef != null && !requestedAuthnContextClassRef.isEmpty())
this.requestedAuthnContextType.addAuthnContextClassRef(requestedAuthnContextClassRef);
for (String requestedAuthnContextDeclRef: this.requestedAuthnContextDeclRefList)
if (requestedAuthnContextDeclRef != null && !requestedAuthnContextDeclRef.isEmpty())
this.requestedAuthnContextType.addAuthnContextDeclRef(requestedAuthnContextDeclRef);
return this.requestedAuthnContextType;
}
}

View file

@ -270,6 +270,17 @@ public class SAMLRequestWriter extends BaseWriter {
}
}
List<String> authnContextDeclRef = requestedAuthnContextType.getAuthnContextDeclRef();
if (authnContextDeclRef != null && !authnContextDeclRef.isEmpty()) {
for (String declRef : authnContextDeclRef) {
StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.AUTHN_CONTEXT_DECL_REF.get(), ASSERTION_NSURI.get());
StaxUtil.writeNameSpace(writer, ASSERTION_PREFIX, ASSERTION_NSURI.get());
StaxUtil.writeCharacters(writer, declRef);
StaxUtil.writeEndElement(writer);
}
}
StaxUtil.writeEndElement(writer);
StaxUtil.flush(writer);
}

View file

@ -45,6 +45,7 @@ import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.saml.validators.DestinationValidator;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.JsonSerialization;
import org.w3c.dom.Element;
@ -54,6 +55,8 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@ -97,6 +100,16 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
}
SAML2RequestedAuthnContextBuilder requestedAuthnContext =
new SAML2RequestedAuthnContextBuilder()
.setComparison(getConfig().getAuthnContextComparisonType());
for (String authnContextClassRef : getAuthnContextClassRefUris())
requestedAuthnContext.addAuthnContextClassRef(authnContextClassRef);
for (String authnContextDeclRef : getAuthnContextDeclRefUris())
requestedAuthnContext.addAuthnContextDeclRef(authnContextDeclRef);
String loginHint = getConfig().isLoginHint() ? request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM) : null;
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
.assertionConsumerUrl(assertionConsumerServiceUrl)
@ -107,6 +120,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.nameIdPolicy(SAML2NameIDPolicyBuilder
.format(nameIDPolicyFormat)
.setAllowCreate(Boolean.TRUE))
.requestedAuthnContext(requestedAuthnContext)
.subject(loginHint);
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session)
@ -148,6 +162,32 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
}
private List<String> getAuthnContextClassRefUris() {
String authnContextClassRefs = getConfig().getAuthnContextClassRefs();
if (authnContextClassRefs == null || authnContextClassRefs.isEmpty())
return new LinkedList<String>();
try {
return Arrays.asList(JsonSerialization.readValue(authnContextClassRefs, String[].class));
} catch (Exception e) {
logger.warn("Could not json-deserialize AuthContextClassRefs config entry: " + authnContextClassRefs, e);
return new LinkedList<String>();
}
}
private List<String> getAuthnContextDeclRefUris() {
String authnContextDeclRefs = getConfig().getAuthnContextDeclRefs();
if (authnContextDeclRefs == null || authnContextDeclRefs.isEmpty())
return new LinkedList<String>();
try {
return Arrays.asList(JsonSerialization.readValue(authnContextDeclRefs, String[].class));
} catch (Exception e) {
logger.warn("Could not json-deserialize AuthContextDeclRefs config entry: " + authnContextDeclRefs, e);
return new LinkedList<String>();
}
}
@Override
public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context) {
ResponseType responseType = (ResponseType)context.getContextData().get(SAMLEndpoint.SAML_LOGIN_RESPONSE);

View file

@ -19,8 +19,8 @@ package org.keycloak.broker.saml;
import static org.keycloak.common.util.UriUtils.checkUrl;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.dom.saml.v2.protocol.AuthnContextComparisonType;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.saml.SamlPrincipalType;
@ -53,6 +53,9 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public static final String WANT_AUTHN_REQUESTS_SIGNED = "wantAuthnRequestsSigned";
public static final String XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER = "xmlSigKeyInfoKeyNameTransformer";
public static final String ENABLED_FROM_METADATA = "enabledFromMetadata";
public static final String AUTHN_CONTEXT_COMPARISON_TYPE = "authnContextComparisonType";
public static final String AUTHN_CONTEXT_CLASS_REFS = "authnContextClassRefs";
public static final String AUTHN_CONTEXT_DECL_REFS = "authnContextDeclRefs";
public SAMLIdentityProviderConfig() {
}
@ -290,6 +293,30 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put(ENABLED_FROM_METADATA , String.valueOf(enabled));
}
public AuthnContextComparisonType getAuthnContextComparisonType() {
return AuthnContextComparisonType.fromValue(getConfig().getOrDefault(AUTHN_CONTEXT_COMPARISON_TYPE, AuthnContextComparisonType.EXACT.value()));
}
public void setAuthnContextComparisonType(AuthnContextComparisonType authnContextComparisonType) {
getConfig().put(AUTHN_CONTEXT_COMPARISON_TYPE, authnContextComparisonType.value());
}
public String getAuthnContextClassRefs() {
return getConfig().get(AUTHN_CONTEXT_CLASS_REFS);
}
public void setAuthnContextClassRefs(String authnContextClassRefs) {
getConfig().put(AUTHN_CONTEXT_CLASS_REFS, authnContextClassRefs);
}
public String getAuthnContextDeclRefs() {
return getConfig().get(AUTHN_CONTEXT_DECL_REFS);
}
public void setAuthnContextDeclRefs(String authnContextDeclRefs) {
getConfig().put(AUTHN_CONTEXT_DECL_REFS, authnContextDeclRefs);
}
@Override
public void validate(RealmModel realm) {
SslRequired sslRequired = realm.getSslRequired();

View file

@ -0,0 +1,241 @@
package org.keycloak.testsuite.broker;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.dom.saml.v2.protocol.AuthnContextComparisonType;
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.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static org.junit.Assert.assertThat;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.AC_PASSWORD_PROTECTED_TRANSPORT;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
/**
* Final class as it's not intended to be overriden.
*/
public final class KcSamlRequestedAuthnContextBrokerTest extends AbstractBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcSamlBrokerConfiguration.INSTANCE;
}
@Test
public void testNoComparisonTypeNoClassRefsAndNoDeclRefs() throws Exception {
// No comparison type, no classrefs, no declrefs -> No RequestedAuthnContext
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 RequestedAuthnContext element
Element requestedAuthnContextElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), PROTOCOL_NSURI.get(), "RequestedAuthnContext");
Assert.assertThat("RequestedAuthnContext element found in request document, but was not necessary as ClassRef/DeclRefs were not specified", requestedAuthnContextElement, Matchers.nullValue());
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
@Test
public void testComparisonTypeSetNoClassRefsAndNoDeclRefs() throws Exception {
// Comparison type set, no classrefs, no declrefs -> No RequestedAuthnContext
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_COMPARISON_TYPE, AuthnContextComparisonType.MINIMUM.value())
.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 RequestedAuthnContext element
Element requestedAuthnContextElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), PROTOCOL_NSURI.get(), "RequestedAuthnContext");
Assert.assertThat("RequestedAuthnContext element found in request document, but was not necessary as ClassRef/DeclRefs were not specified", requestedAuthnContextElement, Matchers.nullValue());
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
@Test
public void testComparisonTypeSetClassRefsSetNoDeclRefs() throws Exception {
// Comparison type set, classref present, no declrefs -> RequestedAuthnContext with AuthnContextClassRef
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_COMPARISON_TYPE, AuthnContextComparisonType.EXACT.value())
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_CLASS_REFS, "[\"" + AC_PASSWORD_PROTECTED_TRANSPORT.get() + "\"]")
.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 RequestedAuthnContext element
Element requestedAuthnContextElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), PROTOCOL_NSURI.get(), "RequestedAuthnContext");
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement, Matchers.notNullValue());
// Verify the ComparisonType attribute
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement.getAttribute("Comparison"), Matchers.is(AuthnContextComparisonType.EXACT.value()));
// Find the RequestedAuthnContext/ClassRef element
Element requestedAuthnContextClassRefElement = DocumentUtil.getDirectChildElement(requestedAuthnContextElement, ASSERTION_NSURI.get(), "AuthnContextClassRef");
Assert.assertThat("RequestedAuthnContext/AuthnContextClassRef element not found in request document", requestedAuthnContextClassRefElement, Matchers.notNullValue());
// Make sure the RequestedAuthnContext/ClassRef element has the requested value
Assert.assertThat("RequestedAuthnContext/AuthnContextClassRef element does not have the expected value", requestedAuthnContextClassRefElement.getTextContent(), Matchers.is(AC_PASSWORD_PROTECTED_TRANSPORT.get()));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
@Test
public void testComparisonTypeSetNoClassRefsDeclRefsSet() throws Exception {
// Comparison type set, no classref present, declrefs set -> RequestedAuthnContext with AuthnContextDeclRef
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_COMPARISON_TYPE, AuthnContextComparisonType.MINIMUM.value())
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_DECL_REFS, "[\"secure/name/password/icmaolr/uri\"]")
.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 RequestedAuthnContext element
Element requestedAuthnContextElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), PROTOCOL_NSURI.get(), "RequestedAuthnContext");
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement, Matchers.notNullValue());
// Verify the ComparisonType attribute
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement.getAttribute("Comparison"), Matchers.is(AuthnContextComparisonType.MINIMUM.value()));
// Find the RequestedAuthnContext/DeclRef element
Element requestedAuthnContextDeclRefElement = DocumentUtil.getDirectChildElement(requestedAuthnContextElement, ASSERTION_NSURI.get(), "AuthnContextDeclRef");
Assert.assertThat("RequestedAuthnContext/AuthnContextDeclRef element not found in request document", requestedAuthnContextDeclRefElement, Matchers.notNullValue());
// Make sure the RequestedAuthnContext/DeclRef element has the requested value
Assert.assertThat("RequestedAuthnContext/AuthnContextDeclRef element does not have the expected value", requestedAuthnContextDeclRefElement.getTextContent(), Matchers.is("secure/name/password/icmaolr/uri"));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
@Test
public void testNoComparisonTypeClassRefsSetNoDeclRefs() throws Exception {
// Comparison type set, no classref present, declrefs set -> RequestedAuthnContext with comparison Exact and AuthnContextClassRef
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.AUTHN_CONTEXT_CLASS_REFS, "[\"" + AC_PASSWORD_PROTECTED_TRANSPORT.get() + "\"]")
.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 RequestedAuthnContext element
Element requestedAuthnContextElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), PROTOCOL_NSURI.get(), "RequestedAuthnContext");
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement, Matchers.notNullValue());
// Verify the ComparisonType attribute
Assert.assertThat("RequestedAuthnContext element not found in request document", requestedAuthnContextElement.getAttribute("Comparison"), Matchers.is(AuthnContextComparisonType.EXACT.value()));
// Find the RequestedAuthnContext/ClassRef element
Element requestedAuthnContextClassRefElement = DocumentUtil.getDirectChildElement(requestedAuthnContextElement, ASSERTION_NSURI.get(), "AuthnContextClassRef");
Assert.assertThat("RequestedAuthnContext/AuthnContextClassRef element not found in request document", requestedAuthnContextClassRefElement, Matchers.notNullValue());
// Make sure the RequestedAuthnContext/ClassRef element has the requested value
Assert.assertThat("RequestedAuthnContext/AuthnContextClassRef element does not have the expected value", requestedAuthnContextClassRefElement.getTextContent(), Matchers.is(AC_PASSWORD_PROTECTED_TRANSPORT.get()));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
}

View file

@ -699,6 +699,18 @@ validating-x509-certificate.tooltip=The certificate in PEM format that must be u
saml.loginHint=Pass subject
saml.loginHint.tooltip=During login phase, forward an optional login_hint query parameter to SAML AuthnRequest's Subject.
saml.import-from-url.tooltip=Import metadata from a remote IDP SAML entity descriptor.
identity-provider.saml.requested-authncontext=Requested AuthnContext Constraints
identity-provider.saml.requested-authncontext.tooltip=Allows the SP to specify the authentication context requirements of authentication statements returned.
identity-provider.saml.authncontext-comparison-type=Comparison
identity-provider.saml.authncontext-comparison-type.tooltip=Specifies the comparison method used to evaluate the requested context classes or statements. The default is "Exact".
identity-provider.saml.authncontext-comparison-type.exact=Exact
identity-provider.saml.authncontext-comparison-type.minimum=Minimum
identity-provider.saml.authncontext-comparison-type.maximum=Maximum
identity-provider.saml.authncontext-comparison-type.better=Better
identity-provider.saml.authncontext-class-ref=AuthnContext ClassRefs
identity-provider.saml.authncontext-class-ref.tooltip=Ordered list of requested AuthnContext ClassRefs.
identity-provider.saml.authncontext-decl-ref=AuthnContext DeclRefs
identity-provider.saml.authncontext-decl-ref.tooltip=Ordered list of requested AuthnContext DeclRefs.
social.client-id.tooltip=The client identifier registered with the identity provider.
social.client-secret.tooltip=The client secret registered with the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format.
social.default-scopes.tooltip=The scopes to be sent when asking for authorization. See the documentation for possible values, separator and default value'.

View file

@ -1125,6 +1125,35 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
}
};
if (instance && instance.alias) {
try { $scope.authnContextClassRefs = JSON.parse($scope.identityProvider.config.authnContextClassRefs || '[]'); } catch (e) { $scope.authnContextClassRefs = []; }
try { $scope.authnContextDeclRefs = JSON.parse($scope.identityProvider.config.authnContextDeclRefs || '[]'); } catch (e) { $scope.authnContextDeclRefs = []; }
} else {
$scope.authnContextClassRefs = [];
$scope.authnContextDeclRefs = [];
}
$scope.deleteAuthnContextClassRef = function(index) {
$scope.authnContextClassRefs.splice(index, 1);
$scope.identityProvider.config.authnContextClassRefs = JSON.stringify($scope.authnContextClassRefs);
};
$scope.addAuthnContextClassRef = function() {
$scope.authnContextClassRefs.push($scope.newAuthnContextClassRef);
$scope.identityProvider.config.authnContextClassRefs = JSON.stringify($scope.authnContextClassRefs);
$scope.newAuthnContextClassRef = "";
};
$scope.deleteAuthnContextDeclRef = function(index) {
$scope.authnContextDeclRefs.splice(index, 1);
$scope.identityProvider.config.authnContextDeclRefs = JSON.stringify($scope.authnContextDeclRefs);
};
$scope.addAuthnContextDeclRef = function() {
$scope.authnContextDeclRefs.push($scope.newAuthnContextDeclRef);
$scope.identityProvider.config.authnContextDeclRefs = JSON.stringify($scope.authnContextDeclRefs);
$scope.newAuthnContextDeclRef = "";
};
});
module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, TimeUnit, TimeUnit2, serverInfo) {

View file

@ -282,6 +282,70 @@
<kc-tooltip>{{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}}</kc-tooltip>
</div>
</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>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="authnContextComparisonType">{{:: 'identity-provider.saml.authncontext-comparison-type' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="authnContextComparisonType"
ng-init="identityProvider.config.authnContextComparisonType = identityProvider.config.authnContextComparisonType || 'exact'"
ng-model="identityProvider.config.authnContextComparisonType">
<option value="exact">{{:: 'identity-provider.saml.authncontext-comparison-type.exact' | translate}}</option>
<option value="minimum">{{:: 'identity-provider.saml.authncontext-comparison-type.minimum' | translate}}</option>
<option value="maximum">{{:: 'identity-provider.saml.authncontext-comparison-type.maximum' | translate}}</option>
<option value="better">{{:: 'identity-provider.saml.authncontext-comparison-type.better' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'identity-provider.saml.authncontext-comparison-type.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label for="type" class="col-md-2 control-label">{{:: 'identity-provider.saml.authncontext-class-ref' | translate}}</label>
<div class="col-sm-4">
<div class="input-group" ng-repeat="(i, authnContextClassRef) in authnContextClassRefs track by $index">
<input class="form-control" ng-model="authnContextClassRefs[i]">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="deleteAuthnContextClassRef($index)">
<span class="fa fa-minus"></span>
</button>
</div>
</div>
<div class = "input-group">
<input class="form-control" ng-model="newAuthnContextClassRef" id="newAuthnContextClassRef">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="newAuthnContextClassRef.length > 0 && addAuthnContextClassRef()">
<span class="fa fa-plus"></span>
</button>
</div>
</div>
</div>
<kc-tooltip>{{:: 'identity-provider.saml.authncontext-class-ref.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label for="type" class="col-md-2 control-label">{{:: 'identity-provider.saml.authncontext-decl-ref' | translate}}</label>
<div class="col-sm-4">
<div class="input-group" ng-repeat="(i, authnContextDeclRef) in authnContextDeclRefs track by $index">
<input class="form-control" ng-model="authnContextDeclRefs[i]">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="deleteAuthnContextDeclRef($index)">
<span class="fa fa-minus"></span>
</button>
</div>
</div>
<div class = "input-group">
<input class="form-control" ng-model="newAuthnContextDeclRef" id="newAuthnContextDeclRef">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="newAuthnContextDeclRef.length > 0 && addAuthnContextDeclRef()">
<span class="fa fa-plus"></span>
</button>
</div>
</div>
</div>
<kc-tooltip>{{:: 'identity-provider.saml.authncontext-decl-ref.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset data-ng-show="newIdentityProvider">
<legend uncollapsed><span class="text">{{:: 'import-external-idp-config' | translate}}</span> <kc-tooltip>{{:: 'import-external-idp-config.tooltip' | translate}}</kc-tooltip></legend>
<div class="form-group" data-ng-show="newIdentityProvider">