From 6bd7420907f180abb560ede476d902634948c153 Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Thu, 1 Jul 2021 11:54:31 +0200 Subject: [PATCH] KEYCLOAK-17290 SAML Client - Generate AttributeConsumingService SP metadata section --- .../keycloak/saml/SPMetadataDescriptor.java | 43 ++------ .../broker/saml/SAMLIdentityProvider.java | 70 ++++++++++++- .../saml/SAMLIdentityProviderConfig.java | 9 ++ .../saml/mappers/UserAttributeMapper.java | 35 ++++++- .../SamlSPDescriptorClientInstallation.java | 26 +++-- .../SamlMetadataDescriptorUpdater.java | 9 ++ ...amlAttributeConsumingServiceIndexTest.java | 4 +- .../broker/KcSamlSpDescriptorTest.java | 99 +++++++++++++++++++ .../messages/admin-messages_en.properties | 2 + .../realm-identity-provider-saml.html | 7 ++ 10 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 services/src/main/java/org/keycloak/protocol/saml/mappers/SamlMetadataDescriptorUpdater.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java index 8c4fa0f21c..4dae9a06ce 100755 --- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java +++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java @@ -17,27 +17,20 @@ 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.util.Arrays; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.keycloak.saml.common.util.StaxUtil; -import org.keycloak.saml.common.exceptions.ProcessingException; + +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 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.Element; @@ -50,25 +43,10 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_ */ public class SPMetadataDescriptor { - public static String getSPDescriptor(URI binding, URI assertionEndpoint, URI logoutEndpoint, - boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted, - String entityId, String nameIDPolicyFormat, List signingCerts, List 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, + public static EntityDescriptorType buildSPdescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint, boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted, - String entityId, String nameIDPolicyFormat, List signingCerts, List encryptionCerts) - throws XMLStreamException, ProcessingException, ParserConfigurationException + String entityId, String nameIDPolicyFormat, List signingCerts, List encryptionCerts) { - StringWriter sw = new StringWriter(); - XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw); - SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer); - EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId); entityDescriptor.setID(IDGenerator.create("ID_")); @@ -104,9 +82,8 @@ public class SPMetadataDescriptor { spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint); 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) diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java index 5ce7c2eed3..d2f6f07c90 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -22,7 +22,9 @@ import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityProviderDataMarshaller; +import org.keycloak.broker.provider.IdentityProviderMapper; import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.broker.saml.mappers.UserAttributeMapper; import org.keycloak.common.util.PemUtils; import org.keycloak.crypto.Algorithm; 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.NameIDType; 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.LogoutRequestType; import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.events.EventBuilder; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.KeyManager; import org.keycloak.models.KeycloakSession; 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.SamlService; import org.keycloak.protocol.saml.SamlSessionUtils; +import org.keycloak.protocol.saml.mappers.SamlMetadataDescriptorUpdater; import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.saml.SAML2AuthnRequestBuilder; 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.exceptions.ConfigurationException; 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.sig.SAML2Signature; +import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter; import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator; import org.keycloak.saml.validators.DestinationValidator; import org.keycloak.sessions.AuthenticationSessionModel; @@ -74,6 +84,10 @@ import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.xml.crypto.dsig.CanonicalizationMethod; 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.security.KeyPair; import java.util.Arrays; @@ -340,12 +354,13 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider signingKeys = new LinkedList<>(); List encryptionKeys = new LinkedList<>(); @@ -369,9 +384,56 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider 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 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 if (getConfig().isSignSpMetadata()) diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java index de34865b9f..19cfd07d09 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java @@ -61,6 +61,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel { public static final String SIGN_SP_METADATA = "signSpMetadata"; public static final String ALLOW_CREATE = "allowCreate"; public static final String ATTRIBUTE_CONSUMING_SERVICE_INDEX = "attributeConsumingServiceIndex"; + public static final String ATTRIBUTE_CONSUMING_SERVICE_NAME = "attributeConsumingServiceName"; 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 public void validate(RealmModel realm) { SslRequired sslRequired = realm.getSslRequired(); diff --git a/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java index 290105720f..b1ea6ce303 100755 --- a/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java @@ -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.AttributeStatementType; 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.IdentityProviderSyncMode; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.protocol.saml.mappers.SamlMetadataDescriptorUpdater; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.saml.common.util.StringUtil; @@ -44,11 +48,13 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC; + /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAttributeMapper extends AbstractIdentityProviderMapper { +public class UserAttributeMapper extends AbstractIdentityProviderMapper implements SamlMetadataDescriptorUpdater { 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."; } + // 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 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); + } + } + } + } + } + } } diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java index 486bac4c98..430f3af018 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java +++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java @@ -19,7 +19,7 @@ package org.keycloak.protocol.saml.installation; import org.jboss.logging.Logger; 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.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -29,13 +29,17 @@ import org.keycloak.protocol.saml.SamlClient; import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.saml.SPMetadataDescriptor; 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 javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import java.io.StringWriter; import java.net.URI; 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; Element spCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientSigningCertificate()); Element encCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientEncryptingCertificate()); - return SPMetadataDescriptor.getSPDescriptor(loginBinding, logoutBinding, new URI(assertionUrl), new URI(logoutUrl), samlClient.requiresClientSignature(), - samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(), - client.getClientId(), nameIdFormat, Arrays.asList(spCertificate), Arrays.asList(encCertificate)); + + StringWriter sw = new StringWriter(); + 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) { logger.error("Cannot generate SP metadata", ex); return ""; diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/SamlMetadataDescriptorUpdater.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SamlMetadataDescriptorUpdater.java new file mode 100644 index 0000000000..ad6e520847 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SamlMetadataDescriptorUpdater.java @@ -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); +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlAttributeConsumingServiceIndexTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlAttributeConsumingServiceIndexTest.java index 7092690b2c..0119cafe60 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlAttributeConsumingServiceIndexTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlAttributeConsumingServiceIndexTest.java @@ -14,7 +14,9 @@ import java.io.Closeable; import org.junit.Assert; import org.junit.Test; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; +import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI; import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; /** @@ -95,4 +97,4 @@ public final class KcSamlAttributeConsumingServiceIndexTest extends AbstractBrok .execute(); } } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java new file mode 100644 index 0000000000..5377d47e2a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java @@ -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.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")); + } + } + +} diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 3f0483230e..03abe3dd9d 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -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.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-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 identity-provider.saml-config.tooltip=SAML SP and external IDP configuration. single-signon-service-url=Single Sign-On Service URL diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 933f5bebfe..87d423cf3b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -309,6 +309,13 @@ {{:: 'identity-provider.saml.attribute-consuming-service-index.tooltip' | translate}} +
+ +
+ +
+ {{:: 'identity-provider.saml.attribute-consuming-service-name.tooltip' | translate}} +
{{:: 'identity-provider.saml.requested-authncontext' | translate}} {{:: 'identity-provider.saml.requested-authncontext.tooltip' | translate}}