KEYCLOAK-17290 SAML Client - Generate AttributeConsumingService SP metadata section
This commit is contained in:
parent
8260c3c623
commit
6bd7420907
10 changed files with 259 additions and 45 deletions
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "";
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,4 +97,4 @@ public final class KcSamlAttributeConsumingServiceIndexTest extends AbstractBrok
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue