Refactor SAML metadata generation to use the SAMLMetadataWriter class

This commit is contained in:
Luca Leonardo Scorcia 2020-04-22 07:38:39 -04:00 committed by Hynek Mlnařík
parent 90cf478f13
commit d6934c64fd
12 changed files with 406 additions and 247 deletions

View file

@ -177,4 +177,4 @@ public enum JBossSAMLURIConstants {
public static JBossSAMLURIConstants from(String key) { public static JBossSAMLURIConstants from(String key) {
return REVERSE_LOOKUP.from(key); return REVERSE_LOOKUP.from(key);
} }
} }

View file

@ -17,59 +17,111 @@
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.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.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.XMLDSIG_NSURI;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
/** /**
* @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 SPMetadataDescriptor { public class SPMetadataDescriptor {
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, public static String getSPDescriptor(URI binding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted, boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, String signingCerts, String encryptionCerts) { String entityId, String nameIDPolicyFormat, List<Element> signingCerts, List<Element> encryptionCerts)
String descriptor = throws XMLStreamException, ProcessingException, ParserConfigurationException
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" + {
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\" WantAssertionsSigned=\"" + wantAssertionsSigned + "\"\n" +
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n"; StringWriter sw = new StringWriter();
XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId);
SPSSODescriptorType spSSODescriptor = new SPSSODescriptorType(Arrays.asList(PROTOCOL_NSURI.get()));
spSSODescriptor.setAuthnRequestsSigned(wantAuthnRequestsSigned);
spSSODescriptor.setWantAssertionsSigned(wantAssertionsSigned);
spSSODescriptor.addNameIDFormat(nameIDPolicyFormat);
spSSODescriptor.addSingleLogoutService(new EndpointType(binding, logoutEndpoint));
if (wantAuthnRequestsSigned && signingCerts != null) { if (wantAuthnRequestsSigned && signingCerts != null) {
descriptor += signingCerts; for (Element key: signingCerts)
{
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(KeyTypes.SIGNING);
keyDescriptor.setKeyInfo(key);
spSSODescriptor.addKeyDescriptor(keyDescriptor);
}
} }
if (wantAssertionsEncrypted && encryptionCerts != null) { if (wantAssertionsEncrypted && encryptionCerts != null) {
descriptor += encryptionCerts; for (Element key: encryptionCerts)
{
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(KeyTypes.ENCRYPTION);
keyDescriptor.setKeyInfo(key);
spSSODescriptor.addKeyDescriptor(keyDescriptor);
}
} }
descriptor +=
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" + IndexedEndpointType assertionConsumerEndpoint = new IndexedEndpointType(binding, assertionEndpoint);
" <NameIDFormat>" + nameIDPolicyFormat + "\n" + assertionConsumerEndpoint.setIsDefault(true);
" </NameIDFormat>\n" + assertionConsumerEndpoint.setIndex(1);
" <AssertionConsumerService\n" + spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint);
" Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
" index=\"1\" isDefault=\"true\" />\n" + entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spSSODescriptor))));
" </SPSSODescriptor>\n" + metadataWriter.writeEntityDescriptor(entityDescriptor);
"</EntityDescriptor>\n";
return descriptor; return sw.toString();
} }
public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) { public static Element buildKeyInfoElement(String keyName, String pemEncodedCertificate)
if (pemEncodedCertificate == null) { throws javax.xml.parsers.ParserConfigurationException
return ""; {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.newDocument();
Element keyInfo = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:KeyInfo");
if (keyName != null) {
Element keyNameElement = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:KeyName");
keyNameElement.setTextContent(keyName);
keyInfo.appendChild(keyNameElement);
} }
StringBuilder target = new StringBuilder() Element x509Data = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Data");
.append(indentation).append("<KeyDescriptor use=\"").append(purpose).append("\">\n")
.append(indentation).append(" <dsig:KeyInfo").append(declareDSigNamespace ? " xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" : ">\n");
if (keyId != null) { Element x509Certificate = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Certificate");
target.append(indentation).append(" <dsig:KeyName>").append(keyId).append("</dsig:KeyName>\n"); x509Certificate.setTextContent(pemEncodedCertificate);
}
x509Data.appendChild(x509Certificate);
target keyInfo.appendChild(x509Data);
.append(indentation).append(" <dsig:X509Data>\n")
.append(indentation).append(" <dsig:X509Certificate>").append(pemEncodedCertificate).append("</dsig:X509Certificate>\n")
.append(indentation).append(" </dsig:X509Data>\n")
.append(indentation).append(" </dsig:KeyInfo>\n")
.append(indentation).append("</KeyDescriptor>\n")
;
return target.toString(); return keyInfo;
} }
} }

View file

@ -187,8 +187,7 @@ public class SAMLMetadataWriter extends BaseWriter {
public void write(SPSSODescriptorType spSSODescriptor) throws ProcessingException { public void write(SPSSODescriptorType spSSODescriptor) throws ProcessingException {
StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.SP_SSO_DESCRIPTOR.get(), JBossSAMLURIConstants.METADATA_NSURI.get()); StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.SP_SSO_DESCRIPTOR.get(), JBossSAMLURIConstants.METADATA_NSURI.get());
StaxUtil.writeAttribute(writer, new QName(JBossSAMLConstants.PROTOCOL_SUPPORT_ENUMERATION.get()), spSSODescriptor writeProtocolSupportEnumeration(spSSODescriptor.getProtocolSupportEnumeration());
.getProtocolSupportEnumeration().get(0));
// Write the attributes // Write the attributes
Boolean authnSigned = spSSODescriptor.isAuthnRequestsSigned(); Boolean authnSigned = spSSODescriptor.isAuthnRequestsSigned();
@ -250,6 +249,12 @@ public class SAMLMetadataWriter extends BaseWriter {
} }
writeProtocolSupportEnumeration(idpSSODescriptor.getProtocolSupportEnumeration()); writeProtocolSupportEnumeration(idpSSODescriptor.getProtocolSupportEnumeration());
// Get the key descriptors
List<KeyDescriptorType> keyDescriptors = idpSSODescriptor.getKeyDescriptor();
for (KeyDescriptorType keyDescriptor : keyDescriptors) {
writeKeyDescriptor(keyDescriptor);
}
List<IndexedEndpointType> artifactResolutionServices = idpSSODescriptor.getArtifactResolutionService(); List<IndexedEndpointType> artifactResolutionServices = idpSSODescriptor.getArtifactResolutionService();
for (IndexedEndpointType indexedEndpoint : artifactResolutionServices) { for (IndexedEndpointType indexedEndpoint : artifactResolutionServices) {
writeArtifactResolutionService(indexedEndpoint); writeArtifactResolutionService(indexedEndpoint);
@ -260,16 +265,16 @@ public class SAMLMetadataWriter extends BaseWriter {
writeSingleLogoutService(endpoint); writeSingleLogoutService(endpoint);
} }
List<EndpointType> ssoServices = idpSSODescriptor.getSingleSignOnService();
for (EndpointType endpoint : ssoServices) {
writeSingleSignOnService(endpoint);
}
List<String> nameIDFormats = idpSSODescriptor.getNameIDFormat(); List<String> nameIDFormats = idpSSODescriptor.getNameIDFormat();
for (String nameIDFormat : nameIDFormats) { for (String nameIDFormat : nameIDFormats) {
writeNameIDFormat(nameIDFormat); writeNameIDFormat(nameIDFormat);
} }
List<EndpointType> ssoServices = idpSSODescriptor.getSingleSignOnService();
for (EndpointType endpoint : ssoServices) {
writeSingleSignOnService(endpoint);
}
List<AttributeType> attributes = idpSSODescriptor.getAttribute(); List<AttributeType> attributes = idpSSODescriptor.getAttribute();
for (AttributeType attribType : attributes) { for (AttributeType attribType : attributes) {
write(attribType); write(attribType);
@ -550,7 +555,10 @@ public class SAMLMetadataWriter extends BaseWriter {
private void writeNameIDFormat(String nameIDFormat) throws ProcessingException { private void writeNameIDFormat(String nameIDFormat) throws ProcessingException {
StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.NAMEID_FORMAT.get(), JBossSAMLURIConstants.METADATA_NSURI.get()); StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.NAMEID_FORMAT.get(), JBossSAMLURIConstants.METADATA_NSURI.get());
StaxUtil.writeCharacters(writer, nameIDFormat); if (nameIDFormat != null) {
StaxUtil.writeCharacters(writer, nameIDFormat);
}
StaxUtil.writeEndElement(writer); StaxUtil.writeEndElement(writer);
} }
} }

View file

@ -45,12 +45,17 @@ 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;
import org.w3c.dom.Element;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -230,53 +235,52 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
@Override @Override
public Response export(UriInfo uriInfo, RealmModel realm, String format) { public Response export(UriInfo uriInfo, RealmModel realm, String format) {
try
{
URI authnBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri();
String authnBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get(); if (getConfig().isPostBindingAuthnRequest()) {
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
if (getConfig().isPostBindingAuthnRequest()) {
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
}
String endpoint = uriInfo.getBaseUriBuilder()
.path("realms").path(realm.getName())
.path("broker")
.path(getConfig().getAlias())
.path("endpoint")
.build().toString();
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted();
String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
StringBuilder signingKeysString = new StringBuilder();
StringBuilder encryptionKeysString = new StringBuilder();
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm));
for (RsaKeyMetadata key : keys) {
addKeyInfo(signingKeysString, key, KeyTypes.SIGNING.value());
if (key.getStatus() == KeyStatus.ACTIVE) {
addKeyInfo(encryptionKeysString, key, KeyTypes.ENCRYPTION.value());
} }
URI endpoint = uriInfo.getBaseUriBuilder()
.path("realms").path(realm.getName())
.path("broker")
.path(getConfig().getAlias())
.path("endpoint")
.build();
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted();
String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
List<Element> signingKeys = new ArrayList<Element>();
List<Element> encryptionKeys = new ArrayList<Element>();
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm));
for (RsaKeyMetadata key : keys) {
if (key == null || key.getCertificate() == null) continue;
signingKeys.add(SPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate())));
if (key.getStatus() == KeyStatus.ACTIVE)
encryptionKeys.add(SPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate())));
}
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint,
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
entityId, nameIDPolicyFormat, signingKeys, encryptionKeys);
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
} catch (Exception e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
} }
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint,
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
entityId, nameIDPolicyFormat, signingKeysString.toString(), encryptionKeysString.toString());
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}
private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
if (key == null) {
return;
}
target.append(SPMetadataDescriptor.xmlKeyInfo(" ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, true));
} }
public SignatureAlgorithm getSignatureAlgorithm() { public SignatureAlgorithm getSignatureAlgorithm() {

View file

@ -0,0 +1,130 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.saml;
import org.keycloak.dom.saml.v2.metadata.EndpointType;
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
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.exceptions.ProcessingException;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.keycloak.saml.common.util.StaxUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.SAML_HTTP_POST_BINDING;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.SAML_SOAP_BINDING;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.XMLDSIG_NSURI;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
/**
* @version $Revision: 1 $
*/
public class IDPMetadataDescriptor {
public static String getIDPDescriptor(URI loginPostEndpoint, URI loginRedirectEndpoint, URI logoutEndpoint,
String entityId, boolean wantAuthnRequestsSigned, List<Element> signingCerts, List<Element> encryptionCerts)
throws XMLStreamException, ProcessingException, ParserConfigurationException
{
StringWriter sw = new StringWriter();
XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntitiesDescriptorType entitiesDescriptor = new EntitiesDescriptorType();
entitiesDescriptor.setName("urn:keycloak");
EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId);
IDPSSODescriptorType spIDPDescriptor = new IDPSSODescriptorType(Arrays.asList(PROTOCOL_NSURI.get()));
spIDPDescriptor.setWantAuthnRequestsSigned(wantAuthnRequestsSigned);
spIDPDescriptor.addNameIDFormat(NAMEID_FORMAT_PERSISTENT.get());
spIDPDescriptor.addNameIDFormat(NAMEID_FORMAT_TRANSIENT.get());
spIDPDescriptor.addNameIDFormat(NAMEID_FORMAT_UNSPECIFIED.get());
spIDPDescriptor.addNameIDFormat(NAMEID_FORMAT_EMAIL.get());
spIDPDescriptor.addSingleLogoutService(new EndpointType(SAML_HTTP_POST_BINDING.getUri(), logoutEndpoint));
spIDPDescriptor.addSingleLogoutService(new EndpointType(SAML_HTTP_REDIRECT_BINDING.getUri(), logoutEndpoint));
spIDPDescriptor.addSingleSignOnService(new EndpointType(SAML_HTTP_POST_BINDING.getUri(), loginPostEndpoint));
spIDPDescriptor.addSingleSignOnService(new EndpointType(SAML_HTTP_REDIRECT_BINDING.getUri(), loginRedirectEndpoint));
spIDPDescriptor.addSingleSignOnService(new EndpointType(SAML_SOAP_BINDING.getUri(), loginPostEndpoint));
if (wantAuthnRequestsSigned && signingCerts != null) {
for (Element key: signingCerts)
{
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(KeyTypes.SIGNING);
keyDescriptor.setKeyInfo(key);
spIDPDescriptor.addKeyDescriptor(keyDescriptor);
}
}
entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spIDPDescriptor))));
entitiesDescriptor.addEntityDescriptor(entityDescriptor);
metadataWriter.writeEntitiesDescriptor(entitiesDescriptor);
return sw.toString();
}
public static Element buildKeyInfoElement(String keyName, String pemEncodedCertificate)
throws javax.xml.parsers.ParserConfigurationException
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.newDocument();
Element keyInfo = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:KeyInfo");
if (keyName != null) {
Element keyNameElement = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:KeyName");
keyNameElement.setTextContent(keyName);
keyInfo.appendChild(keyNameElement);
}
Element x509Data = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Data");
Element x509Certificate = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Certificate");
x509Certificate.setTextContent(pemEncodedCertificate);
x509Data.appendChild(x509Certificate);
keyInfo.appendChild(x509Data);
return keyInfo;
}
}

View file

@ -63,6 +63,8 @@ import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import org.w3c.dom.Element;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam; import javax.ws.rs.FormParam;
import javax.ws.rs.GET; import javax.ws.rs.GET;
@ -77,7 +79,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
@ -656,38 +660,28 @@ public class SamlService extends AuthorizationEndpointBase {
} }
public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) { public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) {
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
String template;
try {
template = StreamUtil.readString(is, StandardCharsets.UTF_8);
} catch (IOException ex) {
logger.error("Cannot generate IdP metadata", ex);
return "";
}
Properties props = new Properties();
props.put("idp.entityID", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
props.put("idp.sso.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
StringBuilder keysString = new StringBuilder();
Set<KeyWrapper> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list Set<KeyWrapper> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority()) ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1)); : (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getKeys(realm, KeyUse.SIG, Algorithm.RS256)); keys.addAll(session.keys().getKeys(realm, KeyUse.SIG, Algorithm.RS256));
for (KeyWrapper key : keys) {
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
}
props.put("idp.signing.certificates", keysString.toString());
return StringPropertyReplacer.replaceProperties(template, props);
}
private static void addKeyInfo(StringBuilder target, KeyWrapper key, String purpose) { try {
if (key == null) { List<Element> signingKeys = new ArrayList<Element>();
return; for (KeyWrapper key : keys) {
} signingKeys.add(IDPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate())));
}
target.append(SPMetadataDescriptor.xmlKeyInfo(" ", return IDPMetadataDescriptor.getIDPDescriptor(
key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false)); RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL),
RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL),
RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL),
RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(),
true,
signingKeys, null);
} catch (Exception ex) {
logger.error("Cannot generate IdP metadata", ex);
return "";
}
} }
private boolean isClientProtocolCorrect(ClientModel clientModel) { private boolean isClientProtocolCorrect(ClientModel clientModel) {

View file

@ -17,7 +17,9 @@
package org.keycloak.protocol.saml.installation; package org.keycloak.protocol.saml.installation;
import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
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;
@ -28,10 +30,13 @@ 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.w3c.dom.Element;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.net.URI; import java.net.URI;
import org.keycloak.dom.saml.v2.metadata.KeyTypes; import java.util.Arrays;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -39,34 +44,41 @@ import org.keycloak.dom.saml.v2.metadata.KeyTypes;
*/ */
public class SamlSPDescriptorClientInstallation implements ClientInstallationProvider { public class SamlSPDescriptorClientInstallation implements ClientInstallationProvider {
protected static final Logger logger = Logger.getLogger(SamlSPDescriptorClientInstallation.class);
public static final String SAML_CLIENT_INSTALATION_SP_DESCRIPTOR = "saml-sp-descriptor"; public static final String SAML_CLIENT_INSTALATION_SP_DESCRIPTOR = "saml-sp-descriptor";
private static final String FALLBACK_ERROR_URL_STRING = "ERROR:ENDPOINT NOT SET"; private static final String FALLBACK_ERROR_URL_STRING = "ERROR:ENDPOINT_NOT_SET";
public static String getSPDescriptorForClient(ClientModel client) { public static String getSPDescriptorForClient(ClientModel client) {
SamlClient samlClient = new SamlClient(client); try {
String assertionUrl; SamlClient samlClient = new SamlClient(client);
String logoutUrl; String assertionUrl;
String binding; String logoutUrl;
if (samlClient.forcePostBinding()) { URI binding;
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE); if (samlClient.forcePostBinding()) {
logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE); assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
binding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(); logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
} else { //redirect binding binding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE); } else { //redirect binding
logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE); assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
binding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get(); logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
binding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri();
}
if (assertionUrl == null || assertionUrl.trim().isEmpty()) assertionUrl = client.getManagementUrl();
if (assertionUrl == null || assertionUrl.trim().isEmpty()) assertionUrl = FALLBACK_ERROR_URL_STRING;
if (logoutUrl == null || logoutUrl.trim().isEmpty()) logoutUrl = client.getManagementUrl();
if (logoutUrl == null || logoutUrl.trim().isEmpty()) logoutUrl = FALLBACK_ERROR_URL_STRING;
String nameIdFormat = samlClient.getNameIDFormat();
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(binding, new URI(assertionUrl), new URI(logoutUrl), samlClient.requiresClientSignature(),
samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(),
client.getClientId(), nameIdFormat, Arrays.asList(spCertificate), Arrays.asList(encCertificate));
} catch (Exception ex) {
logger.error("Cannot generate SP metadata", ex);
return "";
} }
if (assertionUrl == null || assertionUrl.trim().isEmpty()) assertionUrl = client.getManagementUrl();
if (assertionUrl == null || assertionUrl.trim().isEmpty()) assertionUrl = FALLBACK_ERROR_URL_STRING;
if (logoutUrl == null || logoutUrl.trim().isEmpty()) logoutUrl = client.getManagementUrl();
if (logoutUrl == null || logoutUrl.trim().isEmpty()) logoutUrl = FALLBACK_ERROR_URL_STRING;
String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
String encCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientEncryptingCertificate(), KeyTypes.ENCRYPTION.value(), true);
return SPMetadataDescriptor.getSPDescriptor(binding, assertionUrl, logoutUrl, samlClient.requiresClientSignature(),
samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(),
client.getClientId(), nameIdFormat, spCertificate, encCertificate);
} }
@Override @Override

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<EntitiesDescriptor Name="urn:keycloak" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
<EntityDescriptor entityID="${idp.entityID}">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
${idp.signing.certificates}
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="${idp.sls.HTTP-POST}" />
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="${idp.sso.HTTP-Redirect}" />
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="${idp.sso.HTTP-POST}" />
<SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="${idp.sso.HTTP-Redirect}" />
<SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="${idp.sso.HTTP-POST}" />
</IDPSSODescriptor>
</EntityDescriptor>
</EntitiesDescriptor>

View file

@ -933,9 +933,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
Assert.assertTrue("AuthnRequestsSigned", desc.isAuthnRequestsSigned()); Assert.assertTrue("AuthnRequestsSigned", desc.isAuthnRequestsSigned());
Set<String> expected = new HashSet<>(Arrays.asList( Set<String> expected = new HashSet<>(Arrays.asList(
"urn:oasis:names:tc:SAML:2.0:protocol", "urn:oasis:names:tc:SAML:2.0:protocol"));
"urn:oasis:names:tc:SAML:1.1:protocol",
"http://schemas.xmlsoap.org/ws/2003/07/secext"));
Set<String> actual = new HashSet<>(desc.getProtocolSupportEnumeration()); Set<String> actual = new HashSet<>(desc.getProtocolSupportEnumeration());

View file

@ -48,6 +48,8 @@ import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot; import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.METADATA_NSURI;
/** /**
* Test getting the installation/configuration files for OIDC and SAML. * Test getting the installation/configuration files for OIDC and SAML.
* *
@ -189,10 +191,11 @@ public class InstallationTest extends AbstractClientTest {
} }
@Test @Test
public void testSamlMetadataSpDescriptor() { public void testSamlMetadataSpDescriptor() throws Exception {
String xml = samlClient.getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR); String xml = samlClient.getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR);
assertThat(xml, containsString("<EntityDescriptor")); Document doc = getDocumentFromXmlString(xml);
assertThat(xml, containsString("<SPSSODescriptor")); assertElements(doc, METADATA_NSURI.get(), "EntityDescriptor", null);
assertElements(doc, METADATA_NSURI.get(), "SPSSODescriptor", null);
assertThat(xml, containsString(SAML_NAME)); assertThat(xml, containsString(SAML_NAME));
} }
@ -215,9 +218,9 @@ public class InstallationTest extends AbstractClientTest {
Document doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); Document doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
Map<String, String> attrNamesAndValues = new HashMap<>(); Map<String, String> attrNamesAndValues = new HashMap<>();
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
attrNamesAndValues.put("Location", "ERROR:ENDPOINT NOT SET"); attrNamesAndValues.put("Location", "ERROR:ENDPOINT_NOT_SET");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
//fallback to adminUrl //fallback to adminUrl
@ -226,8 +229,8 @@ public class InstallationTest extends AbstractClientTest {
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
attrNamesAndValues.put("Location", "admin-url"); attrNamesAndValues.put("Location", "admin-url");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
//fine grained //fine grained
@ -241,11 +244,11 @@ public class InstallationTest extends AbstractClientTest {
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
attrNamesAndValues.put("Location", "saml-logout-post-url"); attrNamesAndValues.put("Location", "saml-logout-post-url");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
attrNamesAndValues.put("Location", "saml-assertion-post-url"); attrNamesAndValues.put("Location", "saml-assertion-post-url");
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
} }
assertAdminEvents.assertEvent(getRealmId(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(samlClientId), ResourceType.CLIENT); assertAdminEvents.assertEvent(getRealmId(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(samlClientId), ResourceType.CLIENT);
} }
@ -263,9 +266,9 @@ public class InstallationTest extends AbstractClientTest {
Document doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); Document doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
Map<String, String> attrNamesAndValues = new HashMap<>(); Map<String, String> attrNamesAndValues = new HashMap<>();
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
attrNamesAndValues.put("Location", "ERROR:ENDPOINT NOT SET"); attrNamesAndValues.put("Location", "ERROR:ENDPOINT_NOT_SET");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
//fallback to adminUrl //fallback to adminUrl
@ -274,8 +277,8 @@ public class InstallationTest extends AbstractClientTest {
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
attrNamesAndValues.put("Location", "admin-url"); attrNamesAndValues.put("Location", "admin-url");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
//fine grained //fine grained
@ -288,29 +291,33 @@ public class InstallationTest extends AbstractClientTest {
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR)); doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
attrNamesAndValues.put("Location", "saml-logout-redirect-url"); attrNamesAndValues.put("Location", "saml-logout-redirect-url");
assertElements(doc, "SingleLogoutService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
attrNamesAndValues.clear(); attrNamesAndValues.clear();
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
attrNamesAndValues.put("Location", "saml-assertion-redirect-url"); attrNamesAndValues.put("Location", "saml-assertion-redirect-url");
assertElements(doc, "AssertionConsumerService", attrNamesAndValues); assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
} }
assertAdminEvents.assertEvent(getRealmId(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(samlClientId), ResourceType.CLIENT); assertAdminEvents.assertEvent(getRealmId(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(samlClientId), ResourceType.CLIENT);
} }
private Document getDocumentFromXmlString(String xml) throws SAXException, ParserConfigurationException, IOException { private Document getDocumentFromXmlString(String xml) throws SAXException, ParserConfigurationException, IOException {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource(); InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xml)); is.setCharacterStream(new StringReader(xml));
return db.parse(is); return db.parse(is);
} }
private void assertElements(Document doc, String tagName, Map<String, String> attrNamesAndValues) { private void assertElements(Document doc, String tagNamespace, String tagName, Map<String, String> attrNamesAndValues) {
NodeList elementsByTagName = doc.getElementsByTagName(tagName); NodeList elementsByTagName = doc.getElementsByTagNameNS(tagNamespace, tagName);
assertThat("Expected exactly one " + tagName + " element!", elementsByTagName.getLength(), is(equalTo(1))); assertThat("Expected exactly one " + tagName + " element!", elementsByTagName.getLength(), is(equalTo(1)));
Node element = elementsByTagName.item(0); Node element = elementsByTagName.item(0);
for (String attrName : attrNamesAndValues.keySet()) { if (attrNamesAndValues != null) {
assertThat(element.getAttributes().getNamedItem(attrName).getNodeValue(), containsString(attrNamesAndValues.get(attrName))); for (String attrName : attrNamesAndValues.keySet()) {
assertThat(element.getAttributes().getNamedItem(attrName).getNodeValue(), containsString(attrNamesAndValues.get(attrName)));
}
} }
} }
} }

View file

@ -5,35 +5,35 @@
> >
<IDPSSODescriptor WantAuthnRequestsSigned="true" <IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" <KeyDescriptor use="signing">
Location="http://localhost:8080/auth/realms/master/protocol/saml" /> <dsig:KeyInfo>
<dsig:KeyName>hAoy_sBtpu6FdRVCk7ykihF6Ug-o0pKPK3LN9RYkeqs</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>
MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>
MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhetvOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idfLXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
<SingleLogoutService <SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/auth/realms/master/protocol/saml" /> Location="http://localhost:8080/auth/realms/master/protocol/saml" />
<KeyDescriptor use="signing"> <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<dsig:KeyInfo> <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<dsig:KeyName>hAoy_sBtpu6FdRVCk7ykihF6Ug-o0pKPK3LN9RYkeqs</dsig:KeyName> <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<dsig:X509Data> <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<dsig:X509Certificate> <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc= Location="http://localhost:8080/auth/realms/master/protocol/saml" />
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>
MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhetvOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idfLXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
</IDPSSODescriptor> </IDPSSODescriptor>
</EntityDescriptor> </EntityDescriptor>

View file

@ -5,16 +5,6 @@
> >
<IDPSSODescriptor WantAuthnRequestsSigned="true" <IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
<KeyDescriptor use="signing"> <KeyDescriptor use="signing">
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"> <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
<dsig:X509Data> <dsig:X509Data>
@ -24,5 +14,14 @@
</dsig:X509Data> </dsig:X509Data>
</dsig:KeyInfo> </dsig:KeyInfo>
</KeyDescriptor> </KeyDescriptor>
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
</IDPSSODescriptor> </IDPSSODescriptor>
</EntityDescriptor> </EntityDescriptor>