KEYCLOAK-17290 SAML Client - Generate AttributeConsumingService SP metadata section

This commit is contained in:
Luca Leonardo Scorcia 2021-07-01 11:54:31 +02:00 committed by Hynek Mlnařík
parent 8260c3c623
commit 6bd7420907
10 changed files with 259 additions and 45 deletions

View file

@ -17,27 +17,20 @@
package org.keycloak.saml; package org.keycloak.saml;
import org.keycloak.dom.saml.v2.metadata.EndpointType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import java.io.StringWriter;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException; import org.keycloak.dom.saml.v2.metadata.EndpointType;
import javax.xml.stream.XMLStreamWriter; import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.saml.common.util.StaxUtil; import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator; import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -50,25 +43,10 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_
*/ */
public class SPMetadataDescriptor { public class SPMetadataDescriptor {
public static String getSPDescriptor(URI binding, URI assertionEndpoint, URI logoutEndpoint, public static EntityDescriptorType buildSPdescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<Element> signingCerts, List<Element> encryptionCerts)
throws XMLStreamException, ProcessingException, ParserConfigurationException
{
return getSPDescriptor(binding, binding, assertionEndpoint, logoutEndpoint, wantAuthnRequestsSigned,
wantAssertionsSigned, wantAssertionsEncrypted, entityId, nameIDPolicyFormat, signingCerts,
encryptionCerts);
}
public static String getSPDescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted, boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<Element> signingCerts, List<Element> encryptionCerts) String entityId, String nameIDPolicyFormat, List<Element> signingCerts, List<Element> encryptionCerts)
throws XMLStreamException, ProcessingException, ParserConfigurationException
{ {
StringWriter sw = new StringWriter();
XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId); EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId);
entityDescriptor.setID(IDGenerator.create("ID_")); entityDescriptor.setID(IDGenerator.create("ID_"));
@ -104,9 +82,8 @@ public class SPMetadataDescriptor {
spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint); spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint);
entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spSSODescriptor)))); entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spSSODescriptor))));
metadataWriter.writeEntityDescriptor(entityDescriptor);
return sw.toString(); return entityDescriptor;
} }
public static Element buildKeyInfoElement(String keyName, String pemEncodedCertificate) public static Element buildKeyInfoElement(String keyName, String pemEncodedCertificate)

View file

@ -22,7 +22,9 @@ import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller; import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus; import org.keycloak.crypto.KeyStatus;
@ -31,11 +33,16 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType; import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
import org.keycloak.dom.saml.v2.metadata.RequestedAttributeType;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType; import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -45,6 +52,7 @@ import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlService; import org.keycloak.protocol.saml.SamlService;
import org.keycloak.protocol.saml.SamlSessionUtils; import org.keycloak.protocol.saml.SamlSessionUtils;
import org.keycloak.protocol.saml.mappers.SamlMetadataDescriptorUpdater;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.saml.SAML2AuthnRequestBuilder; import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder; import org.keycloak.saml.SAML2LogoutRequestBuilder;
@ -57,8 +65,10 @@ import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.util.DocumentUtil; import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.common.util.StaxUtil;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request; import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature; import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator; import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.saml.validators.DestinationValidator; import org.keycloak.saml.validators.DestinationValidator;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
@ -74,6 +84,10 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import java.util.stream.Collectors;
import javax.xml.stream.XMLStreamWriter;
import java.io.StringWriter;
import java.net.URI; import java.net.URI;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.Arrays; import java.util.Arrays;
@ -340,12 +354,13 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.path("endpoint") .path("endpoint")
.build(); .build();
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned(); boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned(); boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted(); boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted();
String entityId = getEntityId(uriInfo, realm); String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat(); String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
int attributeConsumingServiceIndex = getConfig().getAttributeConsumingServiceIndex() != null ? getConfig().getAttributeConsumingServiceIndex(): 1;
String attributeConsumingServiceName = getConfig().getAttributeConsumingServiceName();
List<Element> signingKeys = new LinkedList<>(); List<Element> signingKeys = new LinkedList<>();
List<Element> encryptionKeys = new LinkedList<>(); List<Element> encryptionKeys = new LinkedList<>();
@ -369,9 +384,56 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} }
}); });
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, // Prepare the metadata descriptor model
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted, StringWriter sw = new StringWriter();
entityId, nameIDPolicyFormat, signingKeys, encryptionKeys); XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPdescriptor(
authnBinding, authnBinding, endpoint, endpoint,
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
entityId, nameIDPolicyFormat, signingKeys, encryptionKeys);
// Create the AttributeConsumingService
AttributeConsumingServiceType attributeConsumingService = new AttributeConsumingServiceType(attributeConsumingServiceIndex);
attributeConsumingService.setIsDefault(true);
if (attributeConsumingServiceName != null && attributeConsumingServiceName.length() > 0)
{
String currentLocale = realm.getDefaultLocale() == null ? "en": realm.getDefaultLocale();
LocalizedNameType attributeConsumingServiceNameElement = new LocalizedNameType(currentLocale);
attributeConsumingServiceNameElement.setValue(attributeConsumingServiceName);
attributeConsumingService.addServiceName(attributeConsumingServiceNameElement);
}
// Look for the SP descriptor and add the attribute consuming service
for (EntityDescriptorType.EDTChoiceType choiceType: entityDescriptor.getChoiceType()) {
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = choiceType.getDescriptors();
if (descriptors != null) {
for (EntityDescriptorType.EDTDescriptorChoiceType descriptor: descriptors) {
if (descriptor.getSpDescriptor() != null) {
descriptor.getSpDescriptor().addAttributeConsumerService(attributeConsumingService);
}
}
}
}
// Add the attribute mappers
realm.getIdentityProviderMappersByAliasStream(getConfig().getAlias())
.forEach(mapper -> {
IdentityProviderMapper target = (IdentityProviderMapper) session.getKeycloakSessionFactory().getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
if (target instanceof SamlMetadataDescriptorUpdater)
{
SamlMetadataDescriptorUpdater metadataAttrProvider = (SamlMetadataDescriptorUpdater)target;
metadataAttrProvider.updateMetadata(mapper, entityDescriptor);
}
});
// Write the metadata and export it to a string
metadataWriter.writeEntityDescriptor(entityDescriptor);
String descriptor = sw.toString();
// Metadata signing // Metadata signing
if (getConfig().isSignSpMetadata()) if (getConfig().isSignSpMetadata())

View file

@ -61,6 +61,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
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 static final String ATTRIBUTE_CONSUMING_SERVICE_INDEX = "attributeConsumingServiceIndex";
public static final String ATTRIBUTE_CONSUMING_SERVICE_NAME = "attributeConsumingServiceName";
public SAMLIdentityProviderConfig() { public SAMLIdentityProviderConfig() {
} }
@ -370,6 +371,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
} }
} }
public void setAttributeConsumingServiceName(String attributeConsumingServiceName) {
getConfig().put(ATTRIBUTE_CONSUMING_SERVICE_NAME, attributeConsumingServiceName);
}
public String getAttributeConsumingServiceName() {
return getConfig().get(ATTRIBUTE_CONSUMING_SERVICE_NAME);
}
@Override @Override
public void validate(RealmModel realm) { public void validate(RealmModel realm) {
SslRequired sslRequired = realm.getSslRequired(); SslRequired sslRequired = realm.getSslRequired();

View file

@ -25,11 +25,15 @@ import org.keycloak.common.util.CollectionUtil;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.assertion.AttributeType; import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.RequestedAttributeType;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode; import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.saml.mappers.SamlMetadataDescriptorUpdater;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.util.StringUtil; import org.keycloak.saml.common.util.StringUtil;
@ -44,11 +48,13 @@ import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UserAttributeMapper extends AbstractIdentityProviderMapper { public class UserAttributeMapper extends AbstractIdentityProviderMapper implements SamlMetadataDescriptorUpdater {
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
@ -214,4 +220,31 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
return "Import declared saml attribute if it exists in assertion into the specified user property or attribute."; return "Import declared saml attribute if it exists in assertion into the specified user property or attribute.";
} }
// ISpMetadataAttributeProvider interface
@Override
public void updateMetadata(IdentityProviderMapperModel mapperModel, EntityDescriptorType entityDescriptor) {
RequestedAttributeType requestedAttribute = new RequestedAttributeType(mapperModel.getConfig().get(UserAttributeMapper.ATTRIBUTE_NAME));
requestedAttribute.setIsRequired(null);
requestedAttribute.setNameFormat(ATTRIBUTE_FORMAT_BASIC.get());
String attributeFriendlyName = mapperModel.getConfig().get(UserAttributeMapper.ATTRIBUTE_FRIENDLY_NAME);
if (attributeFriendlyName != null && attributeFriendlyName.length() > 0)
requestedAttribute.setFriendlyName(attributeFriendlyName);
// Add the requestedAttribute item to any AttributeConsumingServices
for (EntityDescriptorType.EDTChoiceType choiceType: entityDescriptor.getChoiceType()) {
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = choiceType.getDescriptors();
if (descriptors != null) {
for (EntityDescriptorType.EDTDescriptorChoiceType descriptor: descriptors) {
if (descriptor.getSpDescriptor() != null && descriptor.getSpDescriptor().getAttributeConsumingService() != null) {
for (AttributeConsumingServiceType attributeConsumingService: descriptor.getSpDescriptor().getAttributeConsumingService())
{
attributeConsumingService.addRequestedAttribute(requestedAttribute);
}
}
}
}
}
}
} }

View file

@ -19,7 +19,7 @@ package org.keycloak.protocol.saml.installation;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.dom.saml.v2.metadata.KeyTypes; import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -29,13 +29,17 @@ import org.keycloak.protocol.saml.SamlClient;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.saml.SPMetadataDescriptor; import org.keycloak.saml.SPMetadataDescriptor;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.StaxUtil;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import javax.ws.rs.core.MediaType; import java.io.StringWriter;
import javax.ws.rs.core.Response;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.stream.XMLStreamWriter;
/** /**
@ -90,9 +94,19 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT; if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
Element spCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientSigningCertificate()); Element spCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientSigningCertificate());
Element encCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientEncryptingCertificate()); Element encCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientEncryptingCertificate());
return SPMetadataDescriptor.getSPDescriptor(loginBinding, logoutBinding, new URI(assertionUrl), new URI(logoutUrl), samlClient.requiresClientSignature(),
samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(), StringWriter sw = new StringWriter();
client.getClientId(), nameIdFormat, Arrays.asList(spCertificate), Arrays.asList(encCertificate)); XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPdescriptor(
loginBinding, logoutBinding, new URI(assertionUrl), new URI(logoutUrl),
samlClient.requiresClientSignature(), samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(),
client.getClientId(), nameIdFormat, Arrays.asList(spCertificate), Arrays.asList(encCertificate));
metadataWriter.writeEntityDescriptor(entityDescriptor);
return sw.toString();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Cannot generate SP metadata", ex); logger.error("Cannot generate SP metadata", ex);
return ""; return "";

View file

@ -0,0 +1,9 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.models.IdentityProviderMapperModel;
public interface SamlMetadataDescriptorUpdater
{
void updateMetadata(IdentityProviderMapperModel mapperModel, EntityDescriptorType descriptor);
}

View file

@ -14,7 +14,9 @@ import java.io.Closeable;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
/** /**

View file

@ -0,0 +1,99 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.apache.tools.ant.filters.StringInputStream;
import org.junit.Test;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import java.io.Closeable;
import java.io.IOException;
import java.net.URISyntaxException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcSamlBrokerConfiguration.INSTANCE;
}
@Test
public void testAttributeConsumingServiceIndexInSpMetadata() throws IOException, ParsingException, URISyntaxException {
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.ATTRIBUTE_CONSUMING_SERVICE_INDEX, "15")
.update())
{
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class);
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService().isEmpty(), is(false));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(15));
}
}
@Test
public void testAttributeConsumingServiceNameInSpMetadata() throws IOException, ParsingException, URISyntaxException {
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.ATTRIBUTE_CONSUMING_SERVICE_NAME, "My Attribute Set")
.update())
{
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class);
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService().isEmpty(), is(false));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getServiceName().get(0).getValue(), is("My Attribute Set"));
}
}
@Test
public void testAttributeConsumingServiceMappersInSpMetadata() throws IOException, ParsingException, URISyntaxException {
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.ATTRIBUTE_CONSUMING_SERVICE_INDEX, "12")
.update())
{
IdentityProviderMapperRepresentation attrMapperEmail = new IdentityProviderMapperRepresentation();
attrMapperEmail.setName("attribute-mapper-email");
attrMapperEmail.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapperEmail.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "email_attr_name")
.put(UserAttributeMapper.ATTRIBUTE_FRIENDLY_NAME, "email_attr_friendlyname")
.put(UserAttributeMapper.USER_ATTRIBUTE, "email")
.build());
attrMapperEmail.setIdentityProviderAlias(bc.getIDPAlias());
identityProviderResource.addMapper(attrMapperEmail);
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class);
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService().isEmpty(), is(false));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getRequestedAttribute() != null, is(true));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getRequestedAttribute().isEmpty(), is(false));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getRequestedAttribute().get(0).getName(), is("email_attr_name"));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getRequestedAttribute().get(0).getFriendlyName(), is("email_attr_friendlyname"));
}
}
}

View file

@ -730,6 +730,8 @@ identity-provider.saml.protocol-endpoints.saml=SAML 2.0 Service Provider Metadat
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=Attribute Consuming Service Index
identity-provider.saml.attribute-consuming-service-index.tooltip=Index of the Attribute Consuming Service profile to request during authentication identity-provider.saml.attribute-consuming-service-index.tooltip=Index of the Attribute Consuming Service profile to request during authentication
identity-provider.saml.attribute-consuming-service-name=Attribute Consuming Service Name
identity-provider.saml.attribute-consuming-service-name.tooltip=Name of the Attribute Consuming Service profile to advertise in the SP metadata
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

View file

@ -309,6 +309,13 @@
</div> </div>
<kc-tooltip>{{:: 'identity-provider.saml.attribute-consuming-service-index.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'identity-provider.saml.attribute-consuming-service-index.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="attributeConsumingServiceName">{{:: 'identity-provider.saml.attribute-consuming-service-name' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="identityProvider.config.attributeConsumingServiceName" id="attributeConsumingServiceName"/>
</div>
<kc-tooltip>{{:: 'identity-provider.saml.attribute-consuming-service-name.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>