Refactor SAML metadata generation to use the SAMLMetadataWriter class
This commit is contained in:
parent
90cf478f13
commit
d6934c64fd
12 changed files with 406 additions and 247 deletions
|
@ -17,59 +17,111 @@
|
|||
|
||||
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>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SPMetadataDescriptor {
|
||||
|
||||
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint,
|
||||
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
|
||||
String entityId, String nameIDPolicyFormat, String signingCerts, String encryptionCerts) {
|
||||
String descriptor =
|
||||
"<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";
|
||||
public static String getSPDescriptor(URI binding, URI assertionEndpoint, URI logoutEndpoint,
|
||||
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
|
||||
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);
|
||||
|
||||
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) {
|
||||
descriptor += signingCerts;
|
||||
for (Element key: signingCerts)
|
||||
{
|
||||
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
|
||||
keyDescriptor.setUse(KeyTypes.SIGNING);
|
||||
keyDescriptor.setKeyInfo(key);
|
||||
spSSODescriptor.addKeyDescriptor(keyDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
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" +
|
||||
" <NameIDFormat>" + nameIDPolicyFormat + "\n" +
|
||||
" </NameIDFormat>\n" +
|
||||
" <AssertionConsumerService\n" +
|
||||
" Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
|
||||
" index=\"1\" isDefault=\"true\" />\n" +
|
||||
" </SPSSODescriptor>\n" +
|
||||
"</EntityDescriptor>\n";
|
||||
return descriptor;
|
||||
|
||||
IndexedEndpointType assertionConsumerEndpoint = new IndexedEndpointType(binding, assertionEndpoint);
|
||||
assertionConsumerEndpoint.setIsDefault(true);
|
||||
assertionConsumerEndpoint.setIndex(1);
|
||||
spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint);
|
||||
|
||||
entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spSSODescriptor))));
|
||||
metadataWriter.writeEntityDescriptor(entityDescriptor);
|
||||
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) {
|
||||
if (pemEncodedCertificate == null) {
|
||||
return "";
|
||||
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);
|
||||
}
|
||||
|
||||
StringBuilder target = new StringBuilder()
|
||||
.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");
|
||||
Element x509Data = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Data");
|
||||
|
||||
if (keyId != null) {
|
||||
target.append(indentation).append(" <dsig:KeyName>").append(keyId).append("</dsig:KeyName>\n");
|
||||
}
|
||||
Element x509Certificate = doc.createElementNS(XMLDSIG_NSURI.get(), "ds:X509Certificate");
|
||||
x509Certificate.setTextContent(pemEncodedCertificate);
|
||||
|
||||
target
|
||||
.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")
|
||||
;
|
||||
x509Data.appendChild(x509Certificate);
|
||||
|
||||
return target.toString();
|
||||
keyInfo.appendChild(x509Data);
|
||||
|
||||
return keyInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -187,8 +187,7 @@ public class SAMLMetadataWriter extends BaseWriter {
|
|||
|
||||
public void write(SPSSODescriptorType spSSODescriptor) throws ProcessingException {
|
||||
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
|
||||
.getProtocolSupportEnumeration().get(0));
|
||||
writeProtocolSupportEnumeration(spSSODescriptor.getProtocolSupportEnumeration());
|
||||
|
||||
// Write the attributes
|
||||
Boolean authnSigned = spSSODescriptor.isAuthnRequestsSigned();
|
||||
|
@ -250,6 +249,12 @@ public class SAMLMetadataWriter extends BaseWriter {
|
|||
}
|
||||
writeProtocolSupportEnumeration(idpSSODescriptor.getProtocolSupportEnumeration());
|
||||
|
||||
// Get the key descriptors
|
||||
List<KeyDescriptorType> keyDescriptors = idpSSODescriptor.getKeyDescriptor();
|
||||
for (KeyDescriptorType keyDescriptor : keyDescriptors) {
|
||||
writeKeyDescriptor(keyDescriptor);
|
||||
}
|
||||
|
||||
List<IndexedEndpointType> artifactResolutionServices = idpSSODescriptor.getArtifactResolutionService();
|
||||
for (IndexedEndpointType indexedEndpoint : artifactResolutionServices) {
|
||||
writeArtifactResolutionService(indexedEndpoint);
|
||||
|
@ -260,16 +265,16 @@ public class SAMLMetadataWriter extends BaseWriter {
|
|||
writeSingleLogoutService(endpoint);
|
||||
}
|
||||
|
||||
List<EndpointType> ssoServices = idpSSODescriptor.getSingleSignOnService();
|
||||
for (EndpointType endpoint : ssoServices) {
|
||||
writeSingleSignOnService(endpoint);
|
||||
}
|
||||
|
||||
List<String> nameIDFormats = idpSSODescriptor.getNameIDFormat();
|
||||
for (String nameIDFormat : nameIDFormats) {
|
||||
writeNameIDFormat(nameIDFormat);
|
||||
}
|
||||
|
||||
List<EndpointType> ssoServices = idpSSODescriptor.getSingleSignOnService();
|
||||
for (EndpointType endpoint : ssoServices) {
|
||||
writeSingleSignOnService(endpoint);
|
||||
}
|
||||
|
||||
List<AttributeType> attributes = idpSSODescriptor.getAttribute();
|
||||
for (AttributeType attribType : attributes) {
|
||||
write(attribType);
|
||||
|
@ -550,7 +555,10 @@ public class SAMLMetadataWriter extends BaseWriter {
|
|||
private void writeNameIDFormat(String nameIDFormat) throws ProcessingException {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -45,12 +45,17 @@ import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
|||
import org.keycloak.saml.validators.DestinationValidator;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
@ -230,53 +235,52 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
|
||||
@Override
|
||||
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.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());
|
||||
if (getConfig().isPostBindingAuthnRequest()) {
|
||||
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -63,6 +63,8 @@ import org.keycloak.services.resources.RealmsResource;
|
|||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
|
@ -77,7 +79,9 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
@ -656,38 +660,28 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
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
|
||||
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
||||
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
|
||||
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) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<Element> signingKeys = new ArrayList<Element>();
|
||||
for (KeyWrapper key : keys) {
|
||||
signingKeys.add(IDPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate())));
|
||||
}
|
||||
|
||||
target.append(SPMetadataDescriptor.xmlKeyInfo(" ",
|
||||
key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
|
||||
return IDPMetadataDescriptor.getIDPDescriptor(
|
||||
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) {
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
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.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -28,10 +30,13 @@ import org.keycloak.protocol.saml.SamlProtocol;
|
|||
import org.keycloak.saml.SPMetadataDescriptor;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
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>
|
||||
|
@ -39,34 +44,41 @@ import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
|||
*/
|
||||
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";
|
||||
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) {
|
||||
SamlClient samlClient = new SamlClient(client);
|
||||
String assertionUrl;
|
||||
String logoutUrl;
|
||||
String binding;
|
||||
if (samlClient.forcePostBinding()) {
|
||||
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
|
||||
binding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||
} else { //redirect binding
|
||||
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
|
||||
binding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||
try {
|
||||
SamlClient samlClient = new SamlClient(client);
|
||||
String assertionUrl;
|
||||
String logoutUrl;
|
||||
URI binding;
|
||||
if (samlClient.forcePostBinding()) {
|
||||
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
|
||||
binding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
|
||||
} else { //redirect binding
|
||||
assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
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
|
||||
|
|
|
@ -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>
|
|
@ -933,9 +933,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
|||
Assert.assertTrue("AuthnRequestsSigned", desc.isAuthnRequestsSigned());
|
||||
|
||||
Set<String> expected = new HashSet<>(Arrays.asList(
|
||||
"urn:oasis:names:tc:SAML:2.0:protocol",
|
||||
"urn:oasis:names:tc:SAML:1.1:protocol",
|
||||
"http://schemas.xmlsoap.org/ws/2003/07/secext"));
|
||||
"urn:oasis:names:tc:SAML:2.0:protocol"));
|
||||
|
||||
Set<String> actual = new HashSet<>(desc.getProtocolSupportEnumeration());
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ import static org.junit.Assert.assertThat;
|
|||
import static org.hamcrest.Matchers.*;
|
||||
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.
|
||||
*
|
||||
|
@ -189,10 +191,11 @@ public class InstallationTest extends AbstractClientTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSamlMetadataSpDescriptor() {
|
||||
public void testSamlMetadataSpDescriptor() throws Exception {
|
||||
String xml = samlClient.getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR);
|
||||
assertThat(xml, containsString("<EntityDescriptor"));
|
||||
assertThat(xml, containsString("<SPSSODescriptor"));
|
||||
Document doc = getDocumentFromXmlString(xml);
|
||||
assertElements(doc, METADATA_NSURI.get(), "EntityDescriptor", null);
|
||||
assertElements(doc, METADATA_NSURI.get(), "SPSSODescriptor", null);
|
||||
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));
|
||||
Map<String, String> attrNamesAndValues = new HashMap<>();
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "ERROR:ENDPOINT NOT SET");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.put("Location", "ERROR:ENDPOINT_NOT_SET");
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
|
||||
//fallback to adminUrl
|
||||
|
@ -226,8 +229,8 @@ public class InstallationTest extends AbstractClientTest {
|
|||
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "admin-url");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, "AssertionConsumerService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
|
||||
//fine grained
|
||||
|
@ -241,11 +244,11 @@ public class InstallationTest extends AbstractClientTest {
|
|||
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "saml-logout-post-url");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
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);
|
||||
}
|
||||
|
@ -263,9 +266,9 @@ public class InstallationTest extends AbstractClientTest {
|
|||
Document doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
|
||||
Map<String, String> attrNamesAndValues = new HashMap<>();
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "ERROR:ENDPOINT NOT SET");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.put("Location", "ERROR:ENDPOINT_NOT_SET");
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
|
||||
//fallback to adminUrl
|
||||
|
@ -274,8 +277,8 @@ public class InstallationTest extends AbstractClientTest {
|
|||
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "admin-url");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, "AssertionConsumerService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "AssertionConsumerService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
|
||||
//fine grained
|
||||
|
@ -288,29 +291,33 @@ public class InstallationTest extends AbstractClientTest {
|
|||
doc = getDocumentFromXmlString(updater.getResource().getInstallationProvider(SamlSPDescriptorClientInstallation.SAML_CLIENT_INSTALATION_SP_DESCRIPTOR));
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
attrNamesAndValues.put("Location", "saml-logout-redirect-url");
|
||||
assertElements(doc, "SingleLogoutService", attrNamesAndValues);
|
||||
assertElements(doc, METADATA_NSURI.get(), "SingleLogoutService", attrNamesAndValues);
|
||||
attrNamesAndValues.clear();
|
||||
attrNamesAndValues.put("Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
is.setCharacterStream(new StringReader(xml));
|
||||
return db.parse(is);
|
||||
}
|
||||
|
||||
private void assertElements(Document doc, String tagName, Map<String, String> attrNamesAndValues) {
|
||||
NodeList elementsByTagName = doc.getElementsByTagName(tagName);
|
||||
private void assertElements(Document doc, String tagNamespace, String tagName, Map<String, String> attrNamesAndValues) {
|
||||
NodeList elementsByTagName = doc.getElementsByTagNameNS(tagNamespace, tagName);
|
||||
assertThat("Expected exactly one " + tagName + " element!", elementsByTagName.getLength(), is(equalTo(1)));
|
||||
Node element = elementsByTagName.item(0);
|
||||
|
||||
for (String attrName : attrNamesAndValues.keySet()) {
|
||||
assertThat(element.getAttributes().getNamedItem(attrName).getNodeValue(), containsString(attrNamesAndValues.get(attrName)));
|
||||
if (attrNamesAndValues != null) {
|
||||
for (String attrName : attrNamesAndValues.keySet()) {
|
||||
assertThat(element.getAttributes().getNamedItem(attrName).getNodeValue(), containsString(attrNamesAndValues.get(attrName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,35 +5,35 @@
|
|||
>
|
||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||
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" />
|
||||
<KeyDescriptor use="signing">
|
||||
<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
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
|
||||
<KeyDescriptor use="signing">
|
||||
<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>
|
||||
<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>
|
||||
</EntityDescriptor>
|
||||
|
|
|
@ -5,16 +5,6 @@
|
|||
>
|
||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||
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">
|
||||
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
|
||||
<dsig:X509Data>
|
||||
|
@ -24,5 +14,14 @@
|
|||
</dsig:X509Data>
|
||||
</dsig:KeyInfo>
|
||||
</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>
|
||||
</EntityDescriptor>
|
||||
|
|
Loading…
Reference in a new issue