keycloak-aplcache
Changes
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java 11(+11 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java 46(+25 -21)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java 78(+60 -18)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java 7(+7 -0)
adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java 180(+180 -0)
adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml 0(+0 -0)
adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-invalid.xml 0(+0 -0)
Details
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
index 3960b46..de95d87 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters.saml.config;
import java.io.Serializable;
import java.util.List;
+import org.keycloak.adapters.cloned.AdapterHttpClientConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -157,12 +158,97 @@ public class IDP implements Serializable {
}
}
+ public static class HttpClientConfig implements AdapterHttpClientConfig {
+
+ private String truststore;
+ private String truststorePassword;
+ private String clientKeystore;
+ private String clientKeystorePassword;
+ private boolean allowAnyHostname;
+ private boolean disableTrustManager;
+ private int connectionPoolSize;
+ private String proxyUrl;
+
+ @Override
+ public String getTruststore() {
+ return truststore;
+ }
+
+ public void setTruststore(String truststore) {
+ this.truststore = truststore;
+ }
+
+ @Override
+ public String getTruststorePassword() {
+ return truststorePassword;
+ }
+
+ public void setTruststorePassword(String truststorePassword) {
+ this.truststorePassword = truststorePassword;
+ }
+
+ @Override
+ public String getClientKeystore() {
+ return clientKeystore;
+ }
+
+ public void setClientKeystore(String clientKeystore) {
+ this.clientKeystore = clientKeystore;
+ }
+
+ @Override
+ public String getClientKeystorePassword() {
+ return clientKeystorePassword;
+ }
+
+ public void setClientKeystorePassword(String clientKeystorePassword) {
+ this.clientKeystorePassword = clientKeystorePassword;
+ }
+
+ @Override
+ public boolean isAllowAnyHostname() {
+ return allowAnyHostname;
+ }
+
+ public void setAllowAnyHostname(boolean allowAnyHostname) {
+ this.allowAnyHostname = allowAnyHostname;
+ }
+
+ @Override
+ public boolean isDisableTrustManager() {
+ return disableTrustManager;
+ }
+
+ public void setDisableTrustManager(boolean disableTrustManager) {
+ this.disableTrustManager = disableTrustManager;
+ }
+
+ @Override
+ public int getConnectionPoolSize() {
+ return connectionPoolSize;
+ }
+
+ public void setConnectionPoolSize(int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ }
+
+ @Override
+ public String getProxyUrl() {
+ return proxyUrl;
+ }
+
+ public void setProxyUrl(String proxyUrl) {
+ this.proxyUrl = proxyUrl;
+ }
+ }
+
private String entityID;
private String signatureAlgorithm;
private String signatureCanonicalizationMethod;
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
private List<Key> keys;
+ private AdapterHttpClientConfig httpClientConfig = new HttpClientConfig();
public String getEntityID() {
return entityID;
@@ -212,4 +298,12 @@ public class IDP implements Serializable {
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
}
+ public AdapterHttpClientConfig getHttpClientConfig() {
+ return httpClientConfig;
+ }
+
+ public void setHttpClientConfig(AdapterHttpClientConfig httpClientConfig) {
+ this.httpClientConfig = httpClientConfig;
+ }
+
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 0085a6a..1a3dd04 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -72,4 +72,15 @@ public class ConfigXmlConstants {
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
+
+ public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
+ public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
+ public static final String CLIENT_KEYSTORE_ATTR = "clientKeystore";
+ public static final String CLIENT_KEYSTORE_PASSWORD_ATTR = "clientKeystorePassword";
+ public static final String CONNECTION_POOL_SIZE_ATTR = "connectionPoolSize";
+ public static final String DISABLE_TRUST_MANAGER_ATTR = "disableTrustManager";
+ public static final String PROXY_URL_ATTR = "proxyUrl";
+ public static final String TRUSTSTORE_ATTR = "truststore";
+ public static final String TRUSTSTORE_PASSWORD_ATTR = "truststorePassword";
+
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index ee21620..7af71ba 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -40,6 +40,7 @@ import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.Set;
+import org.keycloak.adapters.cloned.HttpClientBuilder;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -178,36 +179,39 @@ public class DeploymentBuilder {
if (sp.getIdp().getKeys() != null) {
for (Key key : sp.getIdp().getKeys()) {
if (key.isSigning()) {
- if (key.getKeystore() != null) {
- KeyStore keyStore = loadKeystore(resourceLoader, key);
- Certificate cert = null;
- try {
- cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
- } catch (KeyStoreException e) {
- throw new RuntimeException(e);
- }
- idp.setSignatureValidationKey(cert.getPublicKey());
- } else {
- if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
- throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
- }
- try {
- PublicKey publicKey = getPublicKeyFromPem(key);
- idp.setSignatureValidationKey(publicKey);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
+ processSigningKey(idp, key, resourceLoader);
}
}
}
+ idp.setClient(new HttpClientBuilder().build(sp.getIdp().getHttpClientConfig()));
idp.refreshKeyLocatorConfiguration();
return deployment;
}
- protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
+ private void processSigningKey(DefaultSamlDeployment.DefaultIDP idp, Key key, ResourceLoader resourceLoader) throws RuntimeException {
+ PublicKey publicKey;
+ if (key.getKeystore() != null) {
+ KeyStore keyStore = loadKeystore(resourceLoader, key);
+ Certificate cert = null;
+ try {
+ cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+ } catch (KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ publicKey = cert.getPublicKey();
+ } else {
+ if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+ throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
+ }
+ publicKey = getPublicKeyFromPem(key);
+ }
+
+ idp.addSignatureValidationKey(publicKey);
+ }
+
+ protected static PublicKey getPublicKeyFromPem(Key key) {
PublicKey publicKey;
if (key.getPublicKeyPem() != null) {
publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
index e649d1c..be54223 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
@@ -29,6 +29,10 @@ import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.util.List;
+import org.keycloak.adapters.saml.config.IDP.HttpClientConfig;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getAttributeValue;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getBooleanAttributeValue;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getIntegerAttributeValue;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,16 +45,16 @@ public class IDPXmlParser extends AbstractParser {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
IDP idp = new IDP();
- String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+ String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
if (entityID == null) {
throw new ParsingException("entityID must be set on IDP");
}
idp.setEntityID(entityID);
- boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
- idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
- idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
+ boolean signaturesRequired = getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
+ idp.setSignatureCanonicalizationMethod(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
+ idp.setSignatureAlgorithm(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
@@ -75,6 +79,10 @@ public class IDPXmlParser extends AbstractParser {
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
idp.setSingleLogoutService(slo);
+ } else if (tag.equals(ConfigXmlConstants.HTTP_CLIENT_ELEMENT)) {
+ HttpClientConfig config = parseHttpClientElement(xmlEventReader);
+ idp.setHttpClientConfig(config);
+
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
KeysXmlParser parser = new KeysXmlParser();
List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
@@ -90,29 +98,63 @@ public class IDPXmlParser extends AbstractParser {
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
- slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
- slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
- slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
- slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
- slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
- slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
- slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
- slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
+ slo.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+ slo.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+ slo.setValidateRequestSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
+ slo.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+ slo.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+ slo.setSignResponse(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
+ slo.setPostBindingUrl(getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
+ slo.setRedirectBindingUrl(getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
return slo;
}
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
- sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
- sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
- sso.setValidateAssertionSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
- sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
- sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
- sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
+ sso.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+ sso.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+ sso.setValidateAssertionSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
+ sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+ sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+ sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
return sso;
}
+ private HttpClientConfig parseHttpClientElement(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.HTTP_CLIENT_ELEMENT);
+ HttpClientConfig config = new HttpClientConfig();
+
+ config.setAllowAnyHostname(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
+ config.setClientKeystore(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_ATTR));
+ config.setClientKeystorePassword(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_PASSWORD_ATTR));
+ config.setConnectionPoolSize(getIntegerAttributeValue(startElement, ConfigXmlConstants.CONNECTION_POOL_SIZE_ATTR, 0));
+ config.setDisableTrustManager(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
+ config.setProxyUrl(getAttributeValue(startElement, ConfigXmlConstants.PROXY_URL_ATTR));
+ config.setTruststore(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_ATTR));
+ config.setTruststorePassword(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_PASSWORD_ATTR));
+
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
+ break;
+ else
+ continue;
+ }
+
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ return config;
+ }
+
@Override
public boolean supports(QName qname) {
return false;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index 3eeb1f7..be6d682 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -48,6 +48,13 @@ public class SPXmlParser extends AbstractParser {
return str;
}
+ public static int getIntegerAttributeValue(StartElement startElement, String tag, int defaultValue) {
+ String result = getAttributeValue(startElement, tag);
+ if (result == null)
+ return defaultValue;
+ return Integer.valueOf(result);
+ }
+
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
String result = getAttributeValue(startElement, tag);
if (result == null)
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index fcbe1e9..a52cdc2 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -23,9 +23,10 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
import org.apache.http.client.HttpClient;
-import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
import org.keycloak.rotation.CompositeKeyLocator;
import org.keycloak.rotation.HardcodedKeyLocator;
@@ -191,8 +192,9 @@ public class DefaultSamlDeployment implements SamlDeployment {
private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
- private HardcodedKeyLocator hardcodedKeyLocator;
+ private final List<PublicKey> signatureValidationKeys = new LinkedList<>();
private int minTimeBetweenDescriptorRequests;
+ private HttpClient client;
@Override
public String getEntityID() {
@@ -227,14 +229,12 @@ public class DefaultSamlDeployment implements SamlDeployment {
this.entityID = entityID;
}
- public void setSignatureValidationKey(PublicKey signatureValidationKey) {
- this.hardcodedKeyLocator = signatureValidationKey == null ? null : new HardcodedKeyLocator(signatureValidationKey);
- refreshKeyLocatorConfiguration();
+ public void addSignatureValidationKey(PublicKey signatureValidationKey) {
+ this.signatureValidationKeys.add(signatureValidationKey);
}
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
this.singleSignOnService = singleSignOnService;
- refreshKeyLocatorConfiguration();
}
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
@@ -245,18 +245,26 @@ public class DefaultSamlDeployment implements SamlDeployment {
this.signatureValidationKeyLocator.clear();
// When key is set, use that (and only that), otherwise configure dynamic key locator
- if (this.hardcodedKeyLocator != null) {
- this.signatureValidationKeyLocator.add(this.hardcodedKeyLocator);
+ if (! this.signatureValidationKeys.isEmpty()) {
+ this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
} else if (this.singleSignOnService != null) {
String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
- // TODO
- HttpClient httpClient = new HttpClientBuilder().build();
+ HttpClient httpClient = getClient();
SamlDescriptorPublicKeyLocator samlDescriptorPublicKeyLocator =
new SamlDescriptorPublicKeyLocator(
samlDescriptorUrl, this.minTimeBetweenDescriptorRequests, DEFAULT_CACHE_TTL, httpClient);
this.signatureValidationKeyLocator.add(samlDescriptorPublicKeyLocator);
}
}
+
+ @Override
+ public HttpClient getClient() {
+ return this.client;
+ }
+
+ public void setClient(HttpClient client) {
+ this.client = client;
+ }
}
private IDP idp;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index f01b6a1..4442177 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -23,6 +23,7 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Set;
+import org.apache.http.client.HttpClient;
import org.keycloak.rotation.KeyLocator;
/**
@@ -77,6 +78,12 @@ public interface SamlDeployment {
*/
int getMinTimeBetweenDescriptorRequests();
+ /**
+ * Returns {@link HttpClient} instance that will be used for http communication with this IdP.
+ * @return see description
+ */
+ HttpClient getClient();
+
public interface SingleSignOnService {
/**
* Returns {@code true} if the requests to IdP need to be signed by SP key.
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
new file mode 100644
index 0000000..174ea17
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
@@ -0,0 +1,144 @@
+<?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.
+ -->
+
+<xs:schema version="1.0"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="urn:keycloak:saml:adapter"
+ targetNamespace="urn:keycloak:saml:adapter"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xs:element name="keycloak-saml-adapter" type="adapter-type"/>
+ <xs:complexType name="adapter-type">
+ <xs:annotation>
+ <xs:documentation>
+ <![CDATA[
+ The Keycloak SAML Adapter keycloak-saml.xml config file
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:all>
+ <xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type"/>
+ </xs:all>
+ </xs:complexType>
+
+ <xs:complexType name="sp-type">
+ <xs:all>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required"/>
+ <xs:attribute name="sslPolicy" type="xs:string" use="optional"/>
+ <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional"/>
+ <xs:attribute name="logoutPage" type="xs:string" use="optional"/>
+ <xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
+ <xs:attribute name="isPassive" type="xs:boolean" use="optional"/>
+ <xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="keys-type">
+ <xs:sequence>
+ <xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="key-type">
+ <xs:all>
+ <xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type"/>
+ <xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="signing" type="xs:boolean" use="optional"/>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="key-store-type">
+ <xs:all>
+ <xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type"/>
+ <xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="file" type="xs:string" use="optional"/>
+ <xs:attribute name="resource" type="xs:string" use="optional"/>
+ <xs:attribute name="password" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="private-key-type">
+ <xs:attribute name="alias" type="xs:string" use="required"/>
+ <xs:attribute name="password" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="certificate-type">
+ <xs:attribute name="alias" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="principal-name-mapping-type">
+ <xs:attribute name="policy" type="xs:string" use="required"/>
+ <xs:attribute name="attribute" type="xs:string" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="role-identifiers-type">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type"/>
+ </xs:choice>
+ </xs:complexType>
+ <xs:complexType name="attribute-type">
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="idp-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type"/>
+ <xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1"/>
+ </xs:sequence>
+ <xs:attribute name="entityID" type="xs:string" use="required"/>
+ <xs:attribute name="signaturesRequired" type="xs:boolean" use="required"/>
+ <xs:attribute name="signatureAlgorithm" type="xs:string" use="optional"/>
+ <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional"/>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="sign-on-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="bindingUrl" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="logout-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
+ <xs:attribute name="signResponse" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="postBindingUrl" type="xs:string" use="optional"/>
+ <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="http-client-type">
+ <xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional"/>
+ <xs:attribute name="clientKeystore" type="xs:string" use="optional"/>
+ <xs:attribute name="clientKeystorePassword" type="xs:string" use="optional"/>
+ <xs:attribute name="connectionPoolSize" type="xs:int" use="optional"/>
+ <xs:attribute name="disableTrustManager" type="xs:boolean" use="optional"/>
+ <xs:attribute name="proxyUrl" type="xs:string" use="optional"/>
+ <xs:attribute name="truststore" type="xs:string" use="optional"/>
+ <xs:attribute name="truststorePassword" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+</xs:schema>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
new file mode 100755
index 0000000..10537b3
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.adapters.saml.config.parsers;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.junit.Test;
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import java.io.InputStream;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapterXMLParserTest {
+
+ private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private void testValidationValid(String fileName) throws Exception {
+ InputStream schema = getClass().getResourceAsStream(CURRENT_XSD_LOCATION);
+ InputStream is = getClass().getResourceAsStream(fileName);
+ assertNotNull(is);
+ assertNotNull(schema);
+ StaxParserUtil.validate(is, schema);
+ }
+
+ @Test
+ public void testValidationSimpleFile() throws Exception {
+ testValidationValid("keycloak-saml.xml");
+ }
+
+ @Test
+ public void testValidationMultipleKeys() throws Exception {
+ testValidationValid("keycloak-saml-multiple-signing-keys.xml");
+ }
+
+ @Test
+ public void testValidationWithHttpClient() throws Exception {
+ testValidationValid("keycloak-saml-wth-http-client-settings.xml");
+ }
+
+ @Test
+ public void testValidationKeyInvalid() throws Exception {
+ InputStream schemaIs = KeycloakSamlAdapterXMLParser.class.getResourceAsStream(CURRENT_XSD_LOCATION);
+ InputStream is = getClass().getResourceAsStream("keycloak-saml-invalid.xml");
+ assertNotNull(is);
+ assertNotNull(schemaIs);
+
+ expectedException.expect(ParsingException.class);
+ StaxParserUtil.validate(is, schemaIs);
+ }
+
+ @Test
+ public void testXmlParser() throws Exception {
+ InputStream is = getClass().getResourceAsStream("keycloak-saml.xml");
+ assertNotNull(is);
+ KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+ KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
+ assertNotNull(config);
+ assertEquals(1, config.getSps().size());
+ SP sp = config.getSps().get(0);
+ assertEquals("sp", sp.getEntityID());
+ assertEquals("ssl", sp.getSslPolicy());
+ assertEquals("format", sp.getNameIDPolicyFormat());
+ assertTrue(sp.isForceAuthentication());
+ assertTrue(sp.isIsPassive());
+ assertEquals(2, sp.getKeys().size());
+ Key signing = sp.getKeys().get(0);
+ assertTrue(signing.isSigning());
+ Key.KeyStoreConfig keystore = signing.getKeystore();
+ assertNotNull(keystore);
+ assertEquals("file", keystore.getFile());
+ assertEquals("cp", keystore.getResource());
+ assertEquals("pw", keystore.getPassword());
+ assertEquals("private alias", keystore.getPrivateKeyAlias());
+ assertEquals("private pw", keystore.getPrivateKeyPassword());
+ assertEquals("cert alias", keystore.getCertificateAlias());
+ Key encryption = sp.getKeys().get(1);
+ assertTrue(encryption.isEncryption());
+ assertEquals("private pem", encryption.getPrivateKeyPem());
+ assertEquals("public pem", encryption.getPublicKeyPem());
+ assertEquals("policy", sp.getPrincipalNameMapping().getPolicy());
+ assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
+ assertTrue(sp.getRoleAttributes().size() == 1);
+ assertTrue(sp.getRoleAttributes().contains("member"));
+
+ IDP idp = sp.getIdp();
+ assertEquals("idp", idp.getEntityID());
+ assertEquals("RSA", idp.getSignatureAlgorithm());
+ assertEquals("canon", idp.getSignatureCanonicalizationMethod());
+ assertTrue(idp.getSingleSignOnService().isSignRequest());
+ assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
+ assertEquals("post", idp.getSingleSignOnService().getRequestBinding());
+ assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
+
+ assertTrue(idp.getSingleLogoutService().isSignRequest());
+ assertTrue(idp.getSingleLogoutService().isSignResponse());
+ assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
+ assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
+ assertEquals("redirect", idp.getSingleLogoutService().getRequestBinding());
+ assertEquals("post", idp.getSingleLogoutService().getResponseBinding());
+ assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
+ assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
+
+ assertTrue(idp.getKeys().size() == 1);
+ assertTrue(idp.getKeys().get(0).isSigning());
+ assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
+ }
+
+
+ @Test
+ public void testXmlParserMultipleSigningKeys() throws Exception {
+ InputStream is = getClass().getResourceAsStream("keycloak-saml-multiple-signing-keys.xml");
+ assertNotNull(is);
+ KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+ KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
+ assertNotNull(config);
+ assertEquals(1, config.getSps().size());
+ SP sp = config.getSps().get(0);
+ IDP idp = sp.getIdp();
+
+ assertTrue(idp.getKeys().size() == 4);
+ for (int i = 0; i < 4; i ++) {
+ Key key = idp.getKeys().get(i);
+ assertTrue(key.isSigning());
+ assertEquals("cert pem " + i, idp.getKeys().get(i).getCertificatePem());
+ }
+ }
+
+ @Test
+ public void testXmlParserHttpClientSettings() throws Exception {
+ InputStream is = getClass().getResourceAsStream("keycloak-saml-wth-http-client-settings.xml");
+ assertNotNull(is);
+ KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+ KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
+ assertNotNull(config);
+ assertEquals(1, config.getSps().size());
+ SP sp = config.getSps().get(0);
+ IDP idp = sp.getIdp();
+
+ assertThat(idp.getHttpClientConfig(), notNullValue());
+ assertThat(idp.getHttpClientConfig().getClientKeystore(), is("ks"));
+ assertThat(idp.getHttpClientConfig().getClientKeystorePassword(), is("ks-pwd"));
+ assertThat(idp.getHttpClientConfig().getProxyUrl(), is("pu"));
+ assertThat(idp.getHttpClientConfig().getTruststore(), is("ts"));
+ assertThat(idp.getHttpClientConfig().getTruststorePassword(), is("tsp"));
+ assertThat(idp.getHttpClientConfig().getConnectionPoolSize(), is(42));
+ assertThat(idp.getHttpClientConfig().isAllowAnyHostname(), is(true));
+ assertThat(idp.getHttpClientConfig().isDisableTrustManager(), is(true));
+ }
+}
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
new file mode 100644
index 0000000..33b2f73
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
@@ -0,0 +1,81 @@
+<!--
+ ~ 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.
+ -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
+ <SP entityID="sp"
+ sslPolicy="ssl"
+ nameIDPolicyFormat="format"
+ forceAuthentication="true"
+ isPassive="true">
+ <Keys>
+ <Key signing="true" >
+ <KeyStore file="file" resource="cp" password="pw">
+ <PrivateKey alias="private alias" password="private pw"/>
+ <Certificate alias="cert alias"/>
+ </KeyStore>
+ </Key>
+ <Key encryption="true">
+ <PrivateKeyPem>
+ private pem
+ </PrivateKeyPem>
+ <PublicKeyPem>
+ public pem
+ </PublicKeyPem>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="policy" attribute="attribute"/>
+ <RoleIdentifiers>
+ <Attribute name="member"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp"
+ signatureAlgorithm="RSA"
+ signatureCanonicalizationMethod="canon"
+ signaturesRequired="true"
+ >
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="post"
+ bindingUrl="url"
+ />
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="redirect"
+ responseBinding="post"
+ postBindingUrl="posturl"
+ redirectBindingUrl="redirecturl"
+ />
+ <Keys>
+ <Key signing="true">
+ <CertificatePem>cert pem 0</CertificatePem>
+ </Key>
+ <Key signing="true">
+ <CertificatePem>cert pem 1</CertificatePem>
+ </Key>
+ <Key signing="true">
+ <CertificatePem>cert pem 2</CertificatePem>
+ </Key>
+ <Key signing="true">
+ <CertificatePem>cert pem 3</CertificatePem>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
new file mode 100644
index 0000000..36410e5
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
@@ -0,0 +1,81 @@
+<!--
+ ~ 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.
+ -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
+ <SP entityID="sp"
+ sslPolicy="ssl"
+ nameIDPolicyFormat="format"
+ forceAuthentication="true"
+ isPassive="true">
+ <Keys>
+ <Key signing="true" >
+ <KeyStore file="file" resource="cp" password="pw">
+ <PrivateKey alias="private alias" password="private pw"/>
+ <Certificate alias="cert alias"/>
+ </KeyStore>
+ </Key>
+ <Key encryption="true">
+ <PrivateKeyPem>
+ private pem
+ </PrivateKeyPem>
+ <PublicKeyPem>
+ public pem
+ </PublicKeyPem>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="policy" attribute="attribute"/>
+ <RoleIdentifiers>
+ <Attribute name="member"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp"
+ signatureAlgorithm="RSA"
+ signatureCanonicalizationMethod="canon"
+ signaturesRequired="true"
+ >
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="post"
+ bindingUrl="url"
+ />
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="redirect"
+ responseBinding="post"
+ postBindingUrl="posturl"
+ redirectBindingUrl="redirecturl"
+ />
+ <Keys>
+ <Key signing="true">
+ <CertificatePem>
+ cert pem
+ </CertificatePem>
+ </Key>
+ </Keys>
+ <HttpClient allowAnyHostname="true"
+ clientKeystore="ks" clientKeystorePassword="ks-pwd"
+ connectionPoolSize="42"
+ disableTrustManager="true"
+ proxyUrl="pu"
+ truststore="ts" truststorePassword="tsp"
+ />
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file