diff --git a/.travis.yml b/.travis.yml
index 35c406117fb..ef531a29f2a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,12 @@ env:
global:
- MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m"
+ matrix:
+ - TESTS=old
+ - TESTS=group1
+ - TESTS=group2
+ - TESTS=group3
+ - TESTS=adapter
jdk:
- oraclejdk8
@@ -22,6 +28,6 @@ install:
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
script:
- - mvn test -B
+ - ./travis-run-tests.sh $TESTS
sudo: false
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index 63cdab890cc..e6d658800d6 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
-import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
}
}
- public HttpClient build(AdapterConfig adapterConfig) {
+ public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
/**
* Configures a the proxy to use for auth-server requests if provided.
*
- * If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
+ * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
*
*
* @param adapterConfig
*/
- private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
+ private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
index 32bcf39aa54..e73f88c1da0 100755
--- a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
@@ -127,7 +127,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
InputStream configInputStream = getConfigInputStream(context);
KeycloakDeployment kd;
if (configInputStream == null) {
- log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ log.warning("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
kd = new KeycloakDeployment();
} else {
kd = KeycloakDeploymentBuilder.build(configInputStream);
@@ -196,6 +196,8 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
+ //needed for the EAP6/AS7 adapter relying on the tomcat core adapter
+ facade.getResponse().sendError(401);
return false;
}
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
index e65d9226f06..2398c95c83b 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -92,7 +92,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
- if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
+ if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
diff --git a/adapters/saml/core/nbproject/project.properties b/adapters/saml/core/nbproject/project.properties
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 16dce33f2c0..b01061b3965 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -34,6 +34,7 @@
${maven.build.timestamp}yyyy-MM-dd HH:mm
+
org.keycloak
@@ -70,6 +71,11 @@
junittest
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
new file mode 100644
index 00000000000..5c94fdb964f
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cloned;
+
+/**
+ * Configuration options relevant for configuring http client that can be used by adapter.
+ *
+ * NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
+ *
+ * @author hmlnarik
+ */
+public interface AdapterHttpClientConfig {
+
+ /**
+ * Returns truststore filename.
+ */
+ public String getTruststore();
+
+ /**
+ * Returns truststore password.
+ */
+ public String getTruststorePassword();
+
+ /**
+ * Returns keystore with client keys.
+ */
+ public String getClientKeystore();
+
+ /**
+ * Returns keystore password.
+ */
+ public String getClientKeystorePassword();
+
+ /**
+ * Returns boolean flag whether any hostname verification is done on the server's
+ * certificate, {@code true} means that verification is not done.
+ * @return
+ */
+ public boolean isAllowAnyHostname();
+
+ /**
+ * Returns boolean flag whether any trust management and hostname verification is done.
+ *
+ * NOTE Disabling trust manager is a security hole, so only set this option
+ * if you cannot or do not want to verify the identity of the
+ * host you are communicating with.
+ */
+ public boolean isDisableTrustManager();
+
+ /**
+ * Returns size of connection pool.
+ */
+ public int getConnectionPoolSize();
+
+ /**
+ * Returns URL of HTTP proxy.
+ */
+ public String getProxyUrl();
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
new file mode 100644
index 00000000000..0fa330e5f6e
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cloned;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.util.EntityUtils;
+import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author Hynek Mlnařík
+ */
+public class HttpAdapterUtils {
+
+ public static MultivaluedHashMap downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
+ try {
+ HttpGet httpRequest = new HttpGet(descriptorUrl);
+ HttpResponse response = client.execute(httpRequest);
+ int status = response.getStatusLine().getStatusCode();
+ if (status != HttpStatus.SC_OK) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw new HttpClientAdapterException("Unexpected status = " + status);
+ }
+
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ throw new HttpClientAdapterException("There was no entity.");
+ }
+
+ MultivaluedHashMap res;
+ try (InputStream is = entity.getContent()) {
+ res = extractKeysFromSamlDescriptor(is);
+ }
+
+ EntityUtils.consumeQuietly(entity);
+
+ return res;
+ } catch (IOException | ParsingException e) {
+ throw new HttpClientAdapterException("IO error", e);
+ }
+ }
+
+ /**
+ * Parses SAML descriptor and extracts keys from it.
+ * @param xmlStream
+ * @return List of KeyInfo objects containing keys from the descriptor.
+ * @throws IOException
+ */
+ public static MultivaluedHashMap extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
+ Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
+ return (MultivaluedHashMap) res;
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
new file mode 100644
index 00000000000..e0371adc059
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.cloned;
+
+/**
+ * @author Marek Posolda
+ */
+public class HttpClientAdapterException extends Exception {
+
+ public HttpClientAdapterException(String message) {
+ super(message);
+ }
+
+ public HttpClientAdapterException(String message, Throwable t) {
+ super(message, t);
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
new file mode 100644
index 00000000000..7e26c01488f
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
@@ -0,0 +1,396 @@
+/*
+ * 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.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.keycloak.common.util.EnvUtil;
+import org.keycloak.common.util.KeystoreUtil;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstraction for creating HttpClients. Allows SSL configuration.
+ *
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class HttpClientBuilder {
+ public static enum HostnameVerificationPolicy {
+ /**
+ * Hostname verification is not done on the server's certificate
+ */
+ ANY,
+ /**
+ * Allows wildcards in subdomain names i.e. *.foo.com
+ */
+ WILDCARD,
+ /**
+ * CN must match hostname connecting to
+ */
+ STRICT
+ }
+
+
+ /**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+ private static class PassthroughTrustManager implements X509TrustManager {
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+
+ protected KeyStore truststore;
+ protected KeyStore clientKeyStore;
+ protected String clientPrivateKeyPassword;
+ protected boolean disableTrustManager;
+ protected boolean disableCookieCache = true;
+ protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
+ protected SSLContext sslContext;
+ protected int connectionPoolSize = 100;
+ protected int maxPooledPerRoute = 0;
+ protected long connectionTTL = -1;
+ protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
+ protected HostnameVerifier verifier = null;
+ protected long socketTimeout = -1;
+ protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected long establishConnectionTimeout = -1;
+ protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected HttpHost proxyHost;
+
+
+ /**
+ * Socket inactivity timeout
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
+ this.socketTimeout = timeout;
+ this.socketTimeoutUnits = unit;
+ return this;
+ }
+
+ /**
+ * When trying to make an initial socket connection, what is the timeout?
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
+ this.establishConnectionTimeout = timeout;
+ this.establishConnectionTimeoutUnits = unit;
+ return this;
+ }
+
+ public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
+ this.connectionTTL = ttl;
+ this.connectionTTLUnit = unit;
+ return this;
+ }
+
+ public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
+ this.maxPooledPerRoute = maxPooledPerRoute;
+ return this;
+ }
+
+ public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Disable trust management and hostname verification. NOTE this is a security
+ * hole, so only set this option if you cannot or do not want to verify the identity of the
+ * host you are communicating with.
+ */
+ public HttpClientBuilder disableTrustManager() {
+ this.disableTrustManager = true;
+ return this;
+ }
+
+ public HttpClientBuilder disableCookieCache() {
+ this.disableCookieCache = true;
+ return this;
+ }
+
+ /**
+ * SSL policy used to verify hostnames
+ *
+ * @param policy
+ * @return
+ */
+ public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
+ this.policy = policy;
+ return this;
+ }
+
+
+ public HttpClientBuilder sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ return this;
+ }
+
+ public HttpClientBuilder trustStore(KeyStore truststore) {
+ this.truststore = truststore;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = password;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = new String(password);
+ return this;
+ }
+
+
+ static class VerifierWrapper implements X509HostnameVerifier {
+ protected HostnameVerifier verifier;
+
+ VerifierWrapper(HostnameVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+ @Override
+ public void verify(String host, SSLSocket ssl) throws IOException {
+ if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
+ }
+
+ @Override
+ public void verify(String host, X509Certificate cert) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return verifier.verify(s, sslSession);
+ }
+ }
+
+ public HttpClient build() {
+ X509HostnameVerifier verifier = null;
+ if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
+ else {
+ switch (policy) {
+ case ANY:
+ verifier = new AllowAllHostnameVerifier();
+ break;
+ case WILDCARD:
+ verifier = new BrowserCompatHostnameVerifier();
+ break;
+ case STRICT:
+ verifier = new StrictHostnameVerifier();
+ break;
+ }
+ }
+ try {
+ SSLSocketFactory sslsf = null;
+ SSLContext theContext = sslContext;
+ if (disableTrustManager) {
+ theContext = SSLContext.getInstance("SSL");
+ theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
+ new SecureRandom());
+ verifier = new AllowAllHostnameVerifier();
+ sslsf = new SniSSLSocketFactory(theContext, verifier);
+ } else if (theContext != null) {
+ sslsf = new SniSSLSocketFactory(theContext, verifier);
+ } else if (clientKeyStore != null || truststore != null) {
+ sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
+ } else {
+ final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
+ tlsContext.init(null, null, null);
+ sslsf = new SniSSLSocketFactory(tlsContext, verifier);
+ }
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(
+ new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+ Scheme httpsScheme = new Scheme("https", 443, sslsf);
+ registry.register(httpsScheme);
+ ClientConnectionManager cm = null;
+ if (connectionPoolSize > 0) {
+ ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
+ tcm.setMaxTotal(connectionPoolSize);
+ if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
+ tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
+ cm = tcm;
+
+ } else {
+ cm = new SingleClientConnManager(registry);
+ }
+ BasicHttpParams params = new BasicHttpParams();
+
+ if (proxyHost != null) {
+ params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
+ }
+
+ if (socketTimeout > -1) {
+ HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
+
+ }
+ if (establishConnectionTimeout > -1) {
+ HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
+ }
+ DefaultHttpClient client = new DefaultHttpClient(cm, params);
+
+ if (disableCookieCache) {
+ client.setCookieStore(new CookieStore() {
+ @Override
+ public void addCookie(Cookie cookie) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List getCookies() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean clearExpired(Date date) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void clear() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+ });
+
+ }
+ return client;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public HttpClient build(AdapterHttpClientConfig adapterConfig) {
+ disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
+
+ String truststorePath = adapterConfig.getTruststore();
+ if (truststorePath != null) {
+ truststorePath = EnvUtil.replace(truststorePath);
+ String truststorePassword = adapterConfig.getTruststorePassword();
+ try {
+ this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load truststore", e);
+ }
+ }
+ String clientKeystore = adapterConfig.getClientKeystore();
+ if (clientKeystore != null) {
+ clientKeystore = EnvUtil.replace(clientKeystore);
+ String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
+ try {
+ KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
+ keyStore(clientCertKeystore, clientKeystorePassword);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load keystore", e);
+ }
+ }
+ int size = 10;
+ if (adapterConfig.getConnectionPoolSize() > 0)
+ size = adapterConfig.getConnectionPoolSize();
+ HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
+ if (adapterConfig.isAllowAnyHostname())
+ policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
+ connectionPoolSize(size);
+ hostnameVerification(policy);
+ if (adapterConfig.isDisableTrustManager()) {
+ disableTrustManager();
+ } else {
+ trustStore(truststore);
+ }
+
+ configureProxyForAuthServerIfProvided(adapterConfig);
+
+ return build();
+ }
+
+ /**
+ * Configures a the proxy to use for auth-server requests if provided.
+ *
+ * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
+ * given URL as a proxy server, otherwise the proxy configuration is ignored.
+ *
+ *
+ * @param adapterConfig
+ */
+ private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
+
+ if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
+ return;
+ }
+
+ URI uri = URI.create(adapterConfig.getProxyUrl());
+ this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+ }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
new file mode 100644
index 00000000000..8c064b0ded2
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
@@ -0,0 +1,143 @@
+/*
+ * 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.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.conn.scheme.HostNameResolver;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.protocol.HttpContext;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.AccessController;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
+ *
+ *
+ * Originally copied from keycloak-adapter-core project.
+ *
+ * @author Marko Strukelj
+ * @author Hynek Mlnařík
+ */
+public class SniSSLSocketFactory extends SSLSocketFactory {
+
+ private static final Logger LOG = Logger.getLogger(SniSSLSocketFactory.class.getName());
+
+ public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
+ }
+
+ public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(keystore, keystorePassword, truststore);
+ }
+
+ public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(keystore, keystorePassword);
+ }
+
+ public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(truststore);
+ }
+
+ public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(trustStrategy, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(trustStrategy);
+ }
+
+ public SniSSLSocketFactory(SSLContext sslContext) {
+ super(sslContext);
+ }
+
+ public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
+ super(sslContext, nameResolver);
+ }
+
+ public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
+ super(sslContext, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
+ super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
+ super(socketfactory, hostnameVerifier);
+ }
+
+ public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
+ super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
+ }
+
+ @Override
+ public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
+ return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
+ }
+
+ @Override
+ public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
+ return super.createLayeredSocket(applySNI(socket, target), target, port, context);
+ }
+
+ private Socket applySNI(final Socket socket, String hostname) {
+ if (socket instanceof SSLSocket) {
+ try {
+ Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ @Override
+ public Method run() throws NoSuchMethodException {
+ return socket.getClass().getMethod("setHost", String.class);
+ }
+ });
+
+ setHostMethod.invoke(socket, hostname);
+ LOG.log(Level.FINEST, "Applied SNI to socket for host {0}", hostname);
+ } catch (PrivilegedActionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ LOG.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
+ }
+ }
+ return socket;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
index 305ffeb9bc5..6ddf52c6937 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
@@ -79,7 +79,9 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
}
- binding.signWith(keypair);
+ binding.signWith(null, keypair);
+ // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+ // .addExtension(new KeycloakKeySamlExtensionGenerator());
binding.signDocument();
}
return binding;
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 ee753ade112..a52cdc2ed2d 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,7 +23,14 @@ 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.saml.rotation.SamlDescriptorPublicKeyLocator;
+import org.keycloak.rotation.CompositeKeyLocator;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
/**
* @author Bill Burke
@@ -179,10 +186,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
public static class DefaultIDP implements IDP {
+ private static final int DEFAULT_CACHE_TTL = 24 * 60 * 60;
+
private String entityID;
- private PublicKey signatureValidationKey;
+ private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
+ private final List signatureValidationKeys = new LinkedList<>();
+ private int minTimeBetweenDescriptorRequests;
+ private HttpClient client;
@Override
public String getEntityID() {
@@ -200,16 +212,25 @@ public class DefaultSamlDeployment implements SamlDeployment {
}
@Override
- public PublicKey getSignatureValidationKey() {
- return signatureValidationKey;
+ public KeyLocator getSignatureValidationKeyLocator() {
+ return this.signatureValidationKeyLocator;
+ }
+
+ @Override
+ public int getMinTimeBetweenDescriptorRequests() {
+ return minTimeBetweenDescriptorRequests;
+ }
+
+ public void setMinTimeBetweenDescriptorRequests(int minTimeBetweenDescriptorRequests) {
+ this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests;
}
public void setEntityID(String entityID) {
this.entityID = entityID;
}
- public void setSignatureValidationKey(PublicKey signatureValidationKey) {
- this.signatureValidationKey = signatureValidationKey;
+ public void addSignatureValidationKey(PublicKey signatureValidationKey) {
+ this.signatureValidationKeys.add(signatureValidationKey);
}
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
@@ -219,6 +240,31 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
this.singleLogoutService = singleLogoutService;
}
+
+ public void refreshKeyLocatorConfiguration() {
+ this.signatureValidationKeyLocator.clear();
+
+ // When key is set, use that (and only that), otherwise configure dynamic key locator
+ if (! this.signatureValidationKeys.isEmpty()) {
+ this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
+ } else if (this.singleSignOnService != null) {
+ String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
+ 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 0b82ff24cd9..444217785ba 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
@@ -22,14 +22,18 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
-import java.security.PublicKey;
import java.util.Set;
+import org.apache.http.client.HttpClient;
+import org.keycloak.rotation.KeyLocator;
/**
+ * Represents SAML deployment configuration.
+ *
* @author Bill Burke
* @version $Revision: 1 $
*/
public interface SamlDeployment {
+
enum Binding {
POST,
REDIRECT;
@@ -41,20 +45,68 @@ public interface SamlDeployment {
}
public interface IDP {
+ /**
+ * Returns entity identifier of this IdP.
+ * @return see description.
+ */
String getEntityID();
+ /**
+ * Returns Single sign on service configuration for this IdP.
+ * @return see description.
+ */
SingleSignOnService getSingleSignOnService();
+
+ /**
+ * Returns Single logout service configuration for this IdP.
+ * @return see description.
+ */
SingleLogoutService getSingleLogoutService();
- PublicKey getSignatureValidationKey();
+
+ /**
+ * Returns {@link KeyLocator} looking up public keys used for validation of IdP signatures.
+ * @return see description.
+ */
+ KeyLocator getSignatureValidationKeyLocator();
+
+ /**
+ * Returns minimum time (in seconds) between issuing requests to IdP SAML descriptor.
+ * Used e.g. by {@link KeyLocator} looking up public keys for validation of IdP signatures
+ * to prevent too frequent requests.
+ *
+ * @return see description.
+ */
+ 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.
+ * @return see dscription
+ */
boolean signRequest();
+ /**
+ * Returns {@code true} if the complete response message from IdP should
+ * be checked for valid signature.
+ * @return see dscription
+ */
boolean validateResponseSignature();
+ /**
+ * Returns {@code true} if individual assertions in response from IdP should
+ * be checked for valid signature.
+ * @return see dscription
+ */
boolean validateAssertionSignature();
Binding getRequestBinding();
Binding getResponseBinding();
String getRequestBindingUrl();
}
+
public interface SingleLogoutService {
boolean validateRequestSignature();
boolean validateResponseSignature();
@@ -67,10 +119,19 @@ public interface SamlDeployment {
}
}
+ /**
+ * Returns Identity Provider configuration for this SAML deployment.
+ * @return see description.
+ */
public IDP getIDP();
public boolean isConfigured();
SslRequired getSslRequired();
+
+ /**
+ * Returns entity identifier of this SP.
+ * @return see description.
+ */
String getEntityID();
String getNameIDPolicyFormat();
boolean isForceAuthentication();
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 3960b464b3a..de95d877a70 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 Bill Burke
@@ -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 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 0085a6a27f7..1a3dd048509 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 d6e4bce41a4..7af71bac1e5 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 Bill Burke
@@ -178,35 +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 e649d1c2598..be54223843f 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 Bill Burke
@@ -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 keys = (List)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 3eeb1f74c70..be6d6827051 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/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
new file mode 100644
index 00000000000..0858675c503
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
@@ -0,0 +1,101 @@
+/*
+ * 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.descriptor.parsers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.processing.core.util.NamespaceContext;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Goes through the given XML file and extracts names, certificates and keys from the KeyInfo elements.
+ * @author hmlnarik
+ */
+public class SamlDescriptorIDPKeysExtractor {
+
+ private static final NamespaceContext NS_CONTEXT = new NamespaceContext();
+ static {
+ NS_CONTEXT.addNsUriPair("m", JBossSAMLURIConstants.METADATA_NSURI.get());
+ NS_CONTEXT.addNsUriPair("dsig", JBossSAMLURIConstants.XMLDSIG_NSURI.get());
+ }
+
+ private final KeyInfoFactory kif = KeyInfoFactory.getInstance();
+
+ private final XPathFactory xPathfactory = XPathFactory.newInstance();
+ private final XPath xpath = xPathfactory.newXPath();
+ {
+ xpath.setNamespaceContext(NS_CONTEXT);
+ }
+
+ public MultivaluedHashMap parse(InputStream stream) throws ParsingException {
+ MultivaluedHashMap res = new MultivaluedHashMap<>();
+
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(stream);
+
+ XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
+ NodeList keyDescriptors = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ for (int i = 0; i < keyDescriptors.getLength(); i ++) {
+ Node keyDescriptor = keyDescriptors.item(i);
+ Element keyDescriptorEl = (Element) keyDescriptor;
+ KeyInfo ki = processKeyDescriptor(keyDescriptorEl);
+ if (ki != null) {
+ String use = keyDescriptorEl.getAttribute(JBossSAMLConstants.USE.get());
+ res.add(use, ki);
+ }
+ }
+ } catch (SAXException | IOException | ParserConfigurationException | MarshalException | XPathExpressionException e) {
+ throw new ParsingException("Error parsing SAML descriptor", e);
+ }
+
+ return res;
+ }
+
+ private KeyInfo processKeyDescriptor(Element keyDescriptor) throws MarshalException {
+ NodeList childNodes = keyDescriptor.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), JBossSAMLConstants.KEY_INFO.get());
+
+ if (childNodes.getLength() == 0) {
+ return null;
+ }
+ Node keyInfoNode = childNodes.item(0);
+ return (keyInfoNode == null) ? null : kif.unmarshalKeyInfo(new DOMStructure(keyInfoNode));
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index e9247b3bbef..429d610dd4c 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -64,11 +64,20 @@ import org.w3c.dom.Node;
import java.io.IOException;
import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.Signature;
+import java.security.SignatureException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Element;
/**
*
@@ -257,13 +266,44 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
+ KeyLocator signatureValidationKey = deployment.getIDP().getSignatureValidationKeyLocator();
if (postBinding) {
- verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
+ verifyPostBindingSignature(holder.getSamlDocument(), signatureValidationKey);
} else {
- verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
+ String keyId = getMessageSigningKeyId(holder.getSamlObject());
+ verifyRedirectBindingSignature(paramKey, signatureValidationKey, keyId);
}
}
+ private String getMessageSigningKeyId(SAML2Object doc) {
+ final ExtensionsType extensions;
+ if (doc instanceof RequestAbstractType) {
+ extensions = ((RequestAbstractType) doc).getExtensions();
+ } else if (doc instanceof StatusResponseType) {
+ extensions = ((StatusResponseType) doc).getExtensions();
+ } else {
+ return null;
+ }
+
+ if (extensions == null) {
+ return null;
+ }
+
+ for (Object ext : extensions.getAny()) {
+ if (! (ext instanceof Element)) {
+ continue;
+ }
+
+ String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
+
+ if (res != null) {
+ return res;
+ }
+ }
+
+ return null;
+ }
+
private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
if(statusCode != null && statusCode.getValue()!=null){
String v = statusCode.getValue().toString();
@@ -473,10 +513,10 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
- public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
+ public void verifyPostBindingSignature(Document document, KeyLocator keyLocator) throws VerificationException {
SAML2Signature saml2Signature = new SAML2Signature();
try {
- if (!saml2Signature.validate(document, publicKey)) {
+ if (!saml2Signature.validate(document, keyLocator)) {
throw new VerificationException("Invalid signature on document");
}
} catch (ProcessingException e) {
@@ -484,7 +524,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
}
- public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
+ private void verifyRedirectBindingSignature(String paramKey, KeyLocator keyLocator, String keyId) throws VerificationException {
String request = facade.getRequest().getQueryParamValue(paramKey);
String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
@@ -511,16 +551,80 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
try {
//byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
byte[] decodedSignature = Base64.decode(signature);
+ byte[] rawQueryBytes = rawQuery.getBytes("UTF-8");
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
- Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
- validator.initVerify(publicKey);
- validator.update(rawQuery.getBytes("UTF-8"));
- if (!validator.verify(decodedSignature)) {
+
+ if (! validateRedirectBindingSignature(signatureAlgorithm, rawQueryBytes, decodedSignature, keyLocator, keyId)) {
throw new VerificationException("Invalid query param signature");
}
} catch (Exception e) {
throw new VerificationException(e);
}
}
+
+ private boolean validateRedirectBindingSignature(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, KeyLocator locator, String keyId)
+ throws KeyManagementException, VerificationException {
+ try {
+ Key key;
+ try {
+ key = locator.getKey(keyId);
+ boolean keyLocated = key != null;
+
+ if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
+ return true;
+ }
+
+ if (keyLocated) {
+ return false;
+ }
+ } catch (KeyManagementException ex) {
+ }
+ } catch (SignatureException ex) {
+ log.debug("Verification failed for key %s: %s", keyId, ex);
+ log.trace(ex);
+ }
+
+ if (locator instanceof Iterable) {
+ Iterable availableKeys = (Iterable) locator;
+
+ log.trace("Trying hard to validate XML signature using all available keys.");
+
+ for (Key key : availableKeys) {
+ try {
+ if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
+ return true;
+ }
+ } catch (SignatureException ex) {
+ log.debug("Verification failed: %s", ex);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean validateRedirectBindingSignatureForKey(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, Key key)
+ throws SignatureException {
+ if (key == null) {
+ return false;
+ }
+
+ if (! (key instanceof PublicKey)) {
+ log.warnf("Unusable key for signature validation: %s", key);
+ return false;
+ }
+
+ Signature signature = sigAlg.createSignature(); // todo plugin signature alg
+ try {
+ signature.initVerify((PublicKey) key);
+ } catch (InvalidKeyException ex) {
+ log.warnf(ex, "Unusable key for signature validation: %s", key);
+ return false;
+ }
+
+ signature.update(rawQueryBytes);
+
+ return signature.verify(decodedSignature);
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
index 5c1454f770a..231c4253376 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
@@ -82,8 +82,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
- .signWith(deployment.getSigningKeyPair())
+ .signWith(null, deployment.getSigningKeyPair())
.signDocument();
+ // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+ // .addExtension(new KeycloakKeySamlExtensionGenerator());
}
@@ -113,8 +115,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
- binding.signWith(deployment.getSigningKeyPair())
+ binding.signWith(null, deployment.getSigningKeyPair())
.signDocument();
+ // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+ // .addExtension(new KeycloakKeySamlExtensionGenerator());
}
binding.relayState("logout");
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
new file mode 100644
index 00000000000..7a45fb790ce
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
@@ -0,0 +1,175 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import org.apache.http.client.HttpClient;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.cloned.HttpAdapterUtils;
+import org.keycloak.adapters.cloned.HttpClientAdapterException;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.Time;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.api.util.KeyInfoTools;
+
+/**
+ * This class defines a {@link KeyLocator} that looks up public keys and certificates in IdP's
+ * SAML descriptor (i.e. http://{host}/auth/realms/{realm}/protocol/saml/descriptor).
+ *
+ * Based on {@code JWKPublicKeyLocator}.
+ *
+ * @author hmlnarik
+ */
+public class SamlDescriptorPublicKeyLocator implements KeyLocator, Iterable {
+
+ private static final Logger LOG = Logger.getLogger(SamlDescriptorPublicKeyLocator.class);
+
+ /**
+ * Time between two subsequent requests (in seconds).
+ */
+ private final int minTimeBetweenDescriptorRequests;
+
+ /**
+ * Time to live for cache entries (in seconds).
+ */
+ private final int cacheEntryTtl;
+
+ /**
+ * Target descriptor URL.
+ */
+ private final String descriptorUrl;
+
+ private final Map publicKeyCache = new ConcurrentHashMap<>();
+
+ private final HttpClient client;
+
+ private volatile int lastRequestTime = 0;
+
+ public SamlDescriptorPublicKeyLocator(String descriptorUrl, int minTimeBetweenDescriptorRequests, int cacheEntryTtl, HttpClient httpClient) {
+ this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests <= 0
+ ? 20
+ : minTimeBetweenDescriptorRequests;
+
+ this.descriptorUrl = descriptorUrl;
+ this.cacheEntryTtl = cacheEntryTtl;
+
+ this.client = httpClient;
+ }
+
+ @Override
+ public Key getKey(String kid) throws KeyManagementException {
+ if (kid == null) {
+ LOG.debugf("Invalid key id: %s", kid);
+ return null;
+ }
+
+ LOG.tracef("Requested key id: %s", kid);
+
+ int currentTime = Time.currentTime();
+
+ PublicKey res;
+ if (currentTime > this.lastRequestTime + this.cacheEntryTtl) {
+ LOG.debugf("Performing regular cache cleanup.");
+ res = refreshCertificateCacheAndGet(kid);
+ } else {
+ res = publicKeyCache.get(kid);
+
+ if (res == null) {
+ if (currentTime > this.lastRequestTime + this.minTimeBetweenDescriptorRequests) {
+ res = refreshCertificateCacheAndGet(kid);
+ } else {
+ LOG.debugf("Won't send request to realm SAML descriptor url, timeout not expired. Last request time was %d", lastRequestTime);
+ }
+ }
+ }
+
+ return res;
+ }
+
+ @Override
+ public synchronized void refreshKeyCache() {
+ LOG.info("Forcing key cache cleanup and refresh.");
+ this.publicKeyCache.clear();
+ refreshCertificateCacheAndGet(null);
+ }
+
+ private synchronized PublicKey refreshCertificateCacheAndGet(String kid) {
+ if (this.descriptorUrl == null) {
+ return null;
+ }
+
+ this.lastRequestTime = Time.currentTime();
+
+ LOG.debugf("Refreshing public key cache from %s", this.descriptorUrl);
+ List signingCerts;
+ try {
+ MultivaluedHashMap certs = HttpAdapterUtils.downloadKeysFromSamlDescriptor(client, this.descriptorUrl);
+ signingCerts = certs.get(KeyTypes.SIGNING.value());
+ } catch (HttpClientAdapterException ex) {
+ LOG.error("Could not refresh certificates from the server", ex);
+ return null;
+ }
+
+ if (signingCerts == null) {
+ return null;
+ }
+
+ LOG.debugf("Certificates retrieved from server, filling public key cache");
+
+ // Only clear cache after it is certain that the SAML descriptor has been read successfully
+ this.publicKeyCache.clear();
+
+ for (KeyInfo ki : signingCerts) {
+ KeyName keyName = KeyInfoTools.getKeyName(ki);
+ X509Certificate x509certificate = KeyInfoTools.getX509Certificate(ki);
+ if (x509certificate != null && keyName != null) {
+ LOG.tracef("Registering signing certificate %s", keyName.getName());
+ this.publicKeyCache.put(keyName.getName(), x509certificate.getPublicKey());
+ } else {
+ LOG.tracef("Ignoring certificate %s: %s", keyName, x509certificate);
+ }
+
+ }
+
+ return (kid == null ? null : this.publicKeyCache.get(kid));
+ }
+
+ @Override
+ public String toString() {
+ return "Keys retrieved from SAML descriptor at " + descriptorUrl;
+ }
+
+ @Override
+ public Iterator iterator() {
+ if (this.publicKeyCache.isEmpty()) {
+ refreshCertificateCacheAndGet(null);
+ }
+
+ return this.publicKeyCache.values().iterator();
+ }
+}
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 00000000000..fc9cb5ec7ef
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+
+
+
+ Keycloak SAML Adapter configuration file.
+
+
+
+
+ Describes SAML service provider configuration.
+
+
+
+
+
+
+
+
+
+
+ List of service provider encryption and validation keys.
+
+ If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.
+
+
+
+
+ When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.
+
+
+
+
+ Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
+ By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.
+
+
+
+
+ Describes configuration of SAML identity provider for this service provider.
+
+
+
+
+
+ This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.
+
+
+
+
+ SSL policy the adapter will enforce.
+
+
+
+
+ SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.
+
+
+
+
+ URL of the logout page.
+
+
+
+
+ SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.
+
+
+
+
+ SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.
+
+
+
+
+ The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.
+
+
+
+
+
+
+
+
+ Describes a single key used for signing or encryption.
+
+
+
+
+
+
+
+
+ Java keystore to load keys and certificates from.
+
+
+
+
+ Private key (PEM format)
+
+
+
+
+ Public key (PEM format)
+
+
+
+
+ Certificate key (PEM format)
+
+
+
+
+
+ Flag defining whether the key should be used for signing.
+
+
+
+
+ Flag defining whether the key should be used for encryption
+
+
+
+
+
+
+
+ Private key declaration
+
+
+
+
+ Certificate declaration
+
+
+
+
+
+ File path to the key store.
+
+
+
+
+ WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().
+
+
+
+
+ The password of the key store.
+
+
+
+
+
+
+ Alias that points to the key or cert within the keystore.
+
+
+
+
+ Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.
+
+
+
+
+
+
+ Alias that points to the key or cert within the keystore.
+
+
+
+
+
+
+ Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().
+
+
+
+
+ Name of the SAML assertion attribute to use within.
+
+
+
+
+
+
+
+ This policy just uses whatever the SAML subject value is. This is the default setting
+
+
+
+
+ This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.
+
+
+
+
+
+
+
+
+ All requests must come in via HTTPS.
+
+
+
+
+ Only non-private IP addresses must come over the wire via HTTPS.
+
+
+
+
+ no requests are required to come over via HTTPS.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies SAML attribute to be converted into roles.
+
+
+
+
+
+
+
+ Specifies name of the SAML attribute to be converted into roles.
+
+
+
+
+
+
+
+ Configuration of the login SAML endpoint of the IDP.
+
+
+
+
+ Configuration of the logout SAML endpoint of the IDP
+
+
+
+
+ The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.
+
+
+
+
+ Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.
+
+
+
+
+
+ issuer ID of the IDP.
+
+
+
+
+ If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.
+
+
+
+
+ Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256
+
+
+
+
+ This is the signature canonicalization method that the IDP expects signed documents to use. The default value is http://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.
+
+
+
+
+
+
+
+
+
+
+
+ Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.
+
+
+
+
+ SAML allows the client to request what binding type it wants authn responses to use. The default is that the client will not request a specific binding type for responses.
+
+
+
+
+ This is the URL for the IDP login service that the client will send requests to.
+
+
+
+
+
+
+
+ Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.
+
+
+
+
+ This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.
+
+
+
+
+ This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.
+
+
+
+
+ This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.
+
+
+
+
+
+
+
+ If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
+ is validated via the truststore, but host name validation is not done. This setting should only be used during
+ development and never in production as it will partly disable verification of SSL certificates.
+ This seting may be useful in test environments. The default value is false.
+
+
+
+
+ This is the file path to a keystore file. This keystore contains client certificate
+ for two-way SSL when the adapter makes HTTPS requests to the IDP server.
+
+
+
+
+ Password for the client keystore and for the client's key.
+
+
+
+
+ Defines number of pooled connections.
+
+
+
+
+ If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
+ This setting should only be used during development and never in production as it will disable verification of SSL certificates.
+ The default value is false.
+
+
+
+
+ URL to HTTP proxy to use for HTTP connections.
+
+
+
+
+ The value is the file path to a keystore file. If you prefix the path with classpath:,
+ then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
+ HTTPS communications to the IDP server. Client making HTTPS requests need
+ a way to verify the host of the server they are talking to. This is what the trustore does.
+ The keystore contains one or more trusted host certificates or certificate authorities.
+ You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
+
+
+
+
+
+ Password for the truststore keystore.
+
+
+
+
+
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
new file mode 100644
index 00000000000..2c03ef884d5
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
@@ -0,0 +1,82 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.keycloak.adapters.cloned;
+
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+import static org.hamcrest.CoreMatchers.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.keycloak.adapters.saml.config.parsers.ConfigXmlConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class HttpAdapterUtilsTest {
+
+ private T getContent(List
-
+
nexus-staging
diff --git a/saml-core/nbproject/project.properties b/saml-core/nbproject/project.properties
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 848324097b9..8c08b69f12d 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -53,6 +53,18 @@
org.apache.santuarioxmlsec
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ 1.3
+ test
+
diff --git a/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
new file mode 100644
index 00000000000..4b3cb57a7dd
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
@@ -0,0 +1,159 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * {@link KeyLocator} that represents a list of multiple {@link KeyLocator}s. Key is searched
+ * from the first to the last {@link KeyLocator} in the order given by the list. If there are
+ * multiple {@link KeyLocator}s providing key with the same key ID, the first matching key is
+ * returned.
+ *
+ * @author hmlnarik
+ */
+public class CompositeKeyLocator implements KeyLocator, Iterable {
+
+ private final List keyLocators = new LinkedList<>();
+
+ @Override
+ public Key getKey(String kid) throws KeyManagementException {
+ for (KeyLocator keyLocator : keyLocators) {
+ Key k = keyLocator.getKey(kid);
+ if (k != null) {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void refreshKeyCache() {
+ for (KeyLocator keyLocator : keyLocators) {
+ keyLocator.refreshKeyCache();
+ }
+ }
+
+ /**
+ * Registers a given {@link KeyLocator} as the first {@link KeyLocator}.
+ */
+ public void addFirst(KeyLocator keyLocator) {
+ this.keyLocators.add(0, keyLocator);
+ }
+
+ /**
+ * Registers a given {@link KeyLocator} as the last {@link KeyLocator}.
+ */
+ public void add(KeyLocator keyLocator) {
+ this.keyLocators.add(keyLocator);
+ }
+
+ /**
+ * Clears the list of registered {@link KeyLocator}s
+ */
+ public void clear() {
+ this.keyLocators.clear();
+ }
+
+ @Override
+ public String toString() {
+ if (this.keyLocators.size() == 1) {
+ return this.keyLocators.get(0).toString();
+ }
+
+ StringBuilder sb = new StringBuilder("Key locator chain: [");
+ for (Iterator it = keyLocators.iterator(); it.hasNext();) {
+ KeyLocator keyLocator = it.next();
+ sb.append(keyLocator.toString());
+ if (it.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ return sb.append("]").toString();
+ }
+
+ @Override
+ public Iterator iterator() {
+ final Iterator> iterablesIterator = getKeyLocatorIterators().iterator();
+
+ return new JointKeyIterator(iterablesIterator).iterator();
+ }
+
+ @SuppressWarnings("unchecked")
+ private Iterable> getKeyLocatorIterators() {
+ List> res = new LinkedList<>();
+ for (KeyLocator kl : this.keyLocators) {
+ if (kl instanceof Iterable) {
+ res.add(((Iterable) kl));
+ }
+ }
+ return Collections.unmodifiableCollection(res);
+ }
+
+ private class JointKeyIterator implements Iterable {
+
+ // based on http://stackoverflow.com/a/34126154/6930869
+ private final Iterator> iterablesIterator;
+
+ public JointKeyIterator(Iterator> iterablesIterator) {
+ this.iterablesIterator = iterablesIterator;
+ }
+
+ @Override
+ public Iterator iterator() {
+ if (! iterablesIterator.hasNext()) {
+ return Collections.emptyIterator();
+ }
+
+ return new Iterator() {
+ private Iterator currentIterator = nextIterator();
+
+ @Override
+ public boolean hasNext() {
+ return currentIterator.hasNext();
+ }
+
+ @Override
+ public Key next() {
+ final Key next = currentIterator.next();
+ findNext();
+ return next;
+ }
+
+ private Iterator nextIterator() {
+ return iterablesIterator.next().iterator();
+ }
+
+ private Iterator findNext() {
+ while (! currentIterator.hasNext()) {
+ if (! iterablesIterator.hasNext()) {
+ break;
+ }
+ currentIterator = nextIterator();
+ }
+ return this;
+ }
+ }.findNext();
+ }
+ }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
new file mode 100644
index 00000000000..ae2615a07b4
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Key locator that always returns a specified key.
+ *
+ * @author Hynek Mlnařík
+ */
+public class HardcodedKeyLocator implements KeyLocator, Iterable {
+
+ private final Collection extends Key> keys;
+
+ public HardcodedKeyLocator(Key key) {
+ this.keys = Collections.singleton(key);
+ }
+
+ public HardcodedKeyLocator(Collection extends Key> keys) {
+ if (keys == null) {
+ throw new NullPointerException("keys");
+ }
+ this.keys = new LinkedList<>(keys);
+ }
+
+ @Override
+ public Key getKey(String kid) {
+ if (this.keys.size() == 1) {
+ return this.keys.iterator().next();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void refreshKeyCache() {
+ // do nothing
+ }
+
+ @Override
+ public String toString() {
+ return "hardcoded keys, count: " + this.keys.size();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return Collections.unmodifiableCollection(keys).iterator();
+ }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
new file mode 100644
index 00000000000..7112eca2996
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+
+/**
+ * This interface defines a method for obtaining a security key by ID.
+ *
+ * If the {@code KeyLocator} implementor wants to make all its keys available for iteration,
+ * it should implement {@link Iterable}<{@code T extends }{@link Key}> interface.
+ * The base {@code KeyLocator} does not extend this interface to enable {@code KeyLocators}
+ * that do not support listing their keys.
+ *
+ * @author Hynek Mlnařík
+ */
+public interface KeyLocator {
+
+ /**
+ * Returns a key with a particular ID.
+ * @param kid Key ID
+ * @param configuration Configuration
+ * @return key, which should be used for verify signature on given "input"
+ * @throws KeyManagementException
+ */
+ Key getKey(String kid) throws KeyManagementException;
+
+ /**
+ * If this key locator caches keys in any way, forces this cache cleanup
+ * and refreshing the keys.
+ */
+ void refreshKeyCache();
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index 6d84c1301b6..f820a5ece42 100755
--- a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -38,11 +38,14 @@ import javax.crypto.spec.SecretKeySpec;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.namespace.QName;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
+import java.security.SignatureException;
import java.security.cert.X509Certificate;
import static org.keycloak.common.util.HtmlUtils.escapeAttribute;
@@ -55,6 +58,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
public class BaseSAML2BindingBuilder {
protected static final Logger logger = Logger.getLogger(BaseSAML2BindingBuilder.class);
+ protected String signingKeyId;
protected KeyPair signingKeyPair;
protected X509Certificate signingCertificate;
protected boolean sign;
@@ -82,23 +86,27 @@ public class BaseSAML2BindingBuilder {
return (T)this;
}
- public T signWith(KeyPair keyPair) {
+ public T signWith(String signingKeyId, KeyPair keyPair) {
+ this.signingKeyId = signingKeyId;
this.signingKeyPair = keyPair;
return (T)this;
}
- public T signWith(PrivateKey privateKey, PublicKey publicKey) {
+ public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey) {
+ this.signingKeyId = signingKeyId;
this.signingKeyPair = new KeyPair(publicKey, privateKey);
return (T)this;
}
- public T signWith(KeyPair keyPair, X509Certificate cert) {
+ public T signWith(String signingKeyId, KeyPair keyPair, X509Certificate cert) {
+ this.signingKeyId = signingKeyId;
this.signingKeyPair = keyPair;
this.signingCertificate = cert;
return (T)this;
}
- public T signWith(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+ public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+ this.signingKeyId = signingKeyId;
this.signingKeyPair = new KeyPair(publicKey, privateKey);
this.signingCertificate = cert;
return (T)this;
@@ -263,7 +271,7 @@ public class BaseSAML2BindingBuilder {
samlSignature.setX509Certificate(signingCertificate);
}
- samlSignature.signSAMLDocument(samlDocument, signingKeyPair, canonicalizationMethodType);
+ samlSignature.signSAMLDocument(samlDocument, signingKeyId, signingKeyPair, canonicalizationMethodType);
}
public void signAssertion(Document samlDocument) throws ProcessingException {
@@ -333,7 +341,7 @@ public class BaseSAML2BindingBuilder {
public String base64Encoded(Document document) throws ConfigurationException, ProcessingException, IOException {
String documentAsString = DocumentUtil.getDocumentAsString(document);
- logger.debugv("saml docment: {0}", documentAsString);
+ logger.debugv("saml document: {0}", documentAsString);
byte[] responseBytes = documentAsString.getBytes("UTF-8");
return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
@@ -358,7 +366,7 @@ public class BaseSAML2BindingBuilder {
signature.initSign(signingKeyPair.getPrivate());
signature.update(rawQuery.getBytes("UTF-8"));
sig = signature.sign();
- } catch (Exception e) {
+ } catch (InvalidKeyException | UnsupportedEncodingException | SignatureException e) {
throw new ProcessingException(e);
}
String encodedSig = RedirectBindingUtil.base64URLEncode(sig);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 302237e6294..ec4fc28a84b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -25,15 +25,19 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author pedroigor
*/
-public class SAML2AuthnRequestBuilder {
+public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder {
private final AuthnRequestType authnRequestType;
protected String destination;
protected String issuer;
+ protected final List extensions = new LinkedList<>();
public SAML2AuthnRequestBuilder destination(String destination) {
this.destination = destination;
@@ -45,6 +49,12 @@ public class SAML2AuthnRequestBuilder {
return this;
}
+ @Override
+ public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
public SAML2AuthnRequestBuilder() {
try {
this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
@@ -90,6 +100,14 @@ public class SAML2AuthnRequestBuilder {
authnRequestType.setDestination(URI.create(this.destination));
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ authnRequestType.setExtensions(extensionsType);
+ }
+
return new SAML2Request().convert(authnRequestType);
} catch (Exception e) {
throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 99d1c1fca12..6da6799b428 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -17,7 +17,10 @@
package org.keycloak.saml;
+import java.util.LinkedList;
+import java.util.List;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
@@ -32,11 +35,12 @@ import org.w3c.dom.Document;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class SAML2ErrorResponseBuilder {
+public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder {
protected String status;
protected String destination;
protected String issuer;
+ protected final List extensions = new LinkedList<>();
public SAML2ErrorResponseBuilder status(String status) {
this.status = status;
@@ -53,6 +57,11 @@ public class SAML2ErrorResponseBuilder {
return this;
}
+ @Override
+ public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
public Document buildDocument() throws ProcessingException {
@@ -66,6 +75,14 @@ public class SAML2ErrorResponseBuilder {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ statusResponse.setExtensions(extensionsType);
+ }
+
SAML2Response saml2Response = new SAML2Response();
return saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
index 2386edb5099..17dafc737f2 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
@@ -39,6 +39,9 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import static org.keycloak.saml.common.util.StringUtil.isNotNull;
@@ -49,7 +52,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
*
* @author bburke@redhat.com
*/
-public class SAML2LoginResponseBuilder {
+public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder {
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
protected String destination;
@@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
protected String authMethod;
protected String requestIssuer;
protected String sessionIndex;
+ protected final List extensions = new LinkedList<>();
public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
@@ -136,6 +140,12 @@ public class SAML2LoginResponseBuilder {
return this;
}
+ @Override
+ public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
Document samlResponseDocument = null;
@@ -207,6 +217,14 @@ public class SAML2LoginResponseBuilder {
assertion.addStatement(authnStatement);
}
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ responseType.setExtensions(extensionsType);
+ }
+
return responseType;
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
index 99b1cf87e76..d0e81ba49b0 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
@@ -27,18 +27,22 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class SAML2LogoutRequestBuilder {
+public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder {
protected String userPrincipal;
protected String userPrincipalFormat;
protected String sessionIndex;
protected long assertionExpiration;
protected String destination;
protected String issuer;
+ protected final List extensions = new LinkedList<>();
public SAML2LogoutRequestBuilder destination(String destination) {
this.destination = destination;
@@ -50,6 +54,12 @@ public class SAML2LogoutRequestBuilder {
return this;
}
+ @Override
+ public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
/**
* Length of time in seconds the assertion is valid for
* See SAML core specification 2.5.1.2 NotOnOrAfter
@@ -99,6 +109,15 @@ public class SAML2LogoutRequestBuilder {
if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
lort.setDestination(URI.create(destination));
+
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ lort.setExtensions(extensionsType);
+ }
+
return lort;
}
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
index c00a4d4013f..8050e812cfa 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
@@ -31,16 +31,20 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class SAML2LogoutResponseBuilder {
+public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder {
protected String logoutRequestID;
protected String destination;
protected String issuer;
+ protected final List extensions = new LinkedList<>();
public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
this.logoutRequestID = logoutRequestID;
@@ -57,6 +61,11 @@ public class SAML2LogoutResponseBuilder {
return this;
}
+ @Override
+ public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
public Document buildDocument() throws ProcessingException {
Document samlResponse = null;
@@ -77,6 +86,14 @@ public class SAML2LogoutResponseBuilder {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ statusResponse.setExtensions(extensionsType);
+ }
+
SAML2Response saml2Response = new SAML2Response();
samlResponse = saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
index e6c10af640b..9a28137694a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -22,21 +22,14 @@ package org.keycloak.saml;
* @version $Revision: 1 $
*/
public class SPMetadataDescriptor {
- public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String certificatePem) {
+
+ public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
String descriptor =
"\n" +
" \n";
- if (wantAuthnRequestsSigned) {
- descriptor +=
- " \n" +
- " \n" +
- " \n" +
- " \n" + certificatePem + "\n" +
- " \n" +
- " \n" +
- " \n" +
- " \n";
+ if (wantAuthnRequestsSigned && signingCerts != null) {
+ descriptor += signingCerts;
}
descriptor +=
" \n" +
@@ -44,10 +37,34 @@ public class SPMetadataDescriptor {
" \n" +
" \n";
- descriptor +=
+ " index=\"1\" isDefault=\"true\" />\n" +
" \n" +
"\n";
return descriptor;
}
+
+ public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) {
+ if (pemEncodedCertificate == null) {
+ return "";
+ }
+
+ StringBuilder target = new StringBuilder()
+ .append(indentation).append("\n")
+ .append(indentation).append(" \n" : ">\n");
+
+ if (keyId != null) {
+ target.append(indentation).append(" ").append(keyId).append("\n");
+ }
+
+ target
+ .append(indentation).append(" \n")
+ .append(indentation).append(" ").append(pemEncodedCertificate).append("\n")
+ .append(indentation).append(" \n")
+ .append(indentation).append(" \n")
+ .append(indentation).append("\n")
+ ;
+
+ return target.toString();
+ }
+
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
new file mode 100644
index 00000000000..2192df615e5
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * 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.saml;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
+/**
+ * Implementations of this interface are builders that can register <samlp:Extensions>
+ * content providers.
+ *
+ * @author hmlnarik
+ */
+public interface SamlProtocolExtensionsAwareBuilder {
+
+ public interface NodeGenerator {
+ /**
+ * Generate contents of the <samlp:Extensions> tag. When this method is invoked,
+ * the writer has already emitted the <samlp:Extensions> start tag.
+ *
+ * @param writer Writer to use for producing XML output
+ * @throws ProcessingException If any exception fails
+ */
+ void write(XMLStreamWriter writer) throws ProcessingException;
+ }
+
+ /**
+ * Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
+ *
+ * @param extension
+ * @return
+ */
+ T addExtension(NodeGenerator extension);
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
index 76a72ae932a..2f58911090b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
@@ -450,6 +450,11 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">. Found <" + foundElementTag + ">");
}
+ @Override
+ public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
+ return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">. Found <" + foundElementNs + ">");
+ }
+
/*
*(non-Javadoc)
*
@@ -2378,4 +2383,10 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
return new ProcessingException("Wrong audience [" + serviceURL + "].");
}
+ @Override
+ public ProcessingException samlExtensionUnknownChild(Class> clazz) {
+ return new ProcessingException("Unknown child type specified for extension: "
+ + (clazz == null ? "" : clazz.getSimpleName())
+ + ".");
+ }
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
index 09f4301c616..37a775504f2 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
@@ -48,6 +48,8 @@ public interface ErrorCodes {
String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";
+ String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";
+
String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";
String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
index 7ac6a091f8d..91f2f547585 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
@@ -296,6 +296,14 @@ public interface PicketLinkLogger {
*/
RuntimeException parserExpectedTag(String tag, String foundElementTag);
+ /**
+ * @param ns
+ * @param foundElementNs
+ *
+ * @return
+ */
+ RuntimeException parserExpectedNamespace(String ns, String foundElementNs);
+
/**
* @param elementName
*
@@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
RuntimeException parserFeatureNotSupported(String feature);
ProcessingException samlAssertionWrongAudience(String serviceURL);
+
+ ProcessingException samlExtensionUnknownChild(Class> clazz);
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index 5ac8ce1c662..49c8df84ccd 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -35,8 +35,8 @@ import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.parsers.ParserConfigurationException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
-import java.security.PublicKey;
import java.security.cert.X509Certificate;
+import org.keycloak.rotation.KeyLocator;
/**
* Class that deals with SAML2 Signature
@@ -121,7 +121,7 @@ public class SAML2Signature {
* @throws MarshalException
* @throws GeneralSecurityException
*/
- public Document sign(Document doc, String referenceID, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
+ public Document sign(Document doc, String referenceID, String keyId, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
GeneralSecurityException, MarshalException, XMLSignatureException {
String referenceURI = "#" + referenceID;
@@ -130,6 +130,7 @@ public class SAML2Signature {
if (sibling != null) {
SignatureUtilTransferObject dto = new SignatureUtilTransferObject();
dto.setDocumentToBeSigned(doc);
+ dto.setKeyId(keyId);
dto.setKeyPair(keyPair);
dto.setDigestMethod(digestMethod);
dto.setSignatureMethod(signatureMethod);
@@ -142,7 +143,7 @@ public class SAML2Signature {
return XMLSignatureUtil.sign(dto, canonicalizationMethodType);
}
- return XMLSignatureUtil.sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
+ return XMLSignatureUtil.sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
}
/**
@@ -153,12 +154,12 @@ public class SAML2Signature {
*
* @throws org.keycloak.saml.common.exceptions.ProcessingException
*/
- public void signSAMLDocument(Document samlDocument, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
+ public void signSAMLDocument(Document samlDocument, String keyId, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
// Get the ID from the root
String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
try {
- sign(samlDocument, id, keypair, canonicalizationMethodType);
- } catch (Exception e) {
+ sign(samlDocument, id, keyId, keypair, canonicalizationMethodType);
+ } catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
throw new ProcessingException(logger.signatureError(e));
}
}
@@ -167,20 +168,18 @@ public class SAML2Signature {
* Validate the SAML2 Document
*
* @param signedDocument
- * @param publicKey
+ * @param keyLocator
*
* @return
*
* @throws ProcessingException
*/
- public boolean validate(Document signedDocument, PublicKey publicKey) throws ProcessingException {
+ public boolean validate(Document signedDocument, KeyLocator keyLocator) throws ProcessingException {
try {
configureIdAttribute(signedDocument);
- return XMLSignatureUtil.validate(signedDocument, publicKey);
- } catch (MarshalException me) {
+ return XMLSignatureUtil.validate(signedDocument, keyLocator);
+ } catch (MarshalException | XMLSignatureException me) {
throw new ProcessingException(logger.signatureError(me));
- } catch (XMLSignatureException xse) {
- throw new ProcessingException(logger.signatureError(xse));
}
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
new file mode 100644
index 00000000000..be9bf51ddbd
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
@@ -0,0 +1,60 @@
+/*
+ * 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.saml.processing.api.util;
+
+import java.security.cert.X509Certificate;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+/**
+ * Tools for {@link KeyInfo} object manipulation.
+ * @author hmlnarik
+ */
+public class KeyInfoTools {
+
+ /**
+ * Returns the first object of the given class from the given Iterable.
+ * @param
+ * @param objects
+ * @param clazz
+ * @return The object or {@code null} if not found.
+ */
+ public static T getContent(Iterable objects, Class clazz) {
+ for (Object o : objects) {
+ if (clazz.isInstance(o)) {
+ return (T) o;
+ }
+ }
+ return null;
+ }
+
+
+ public static KeyName getKeyName(KeyInfo keyInfo) {
+ return getContent(keyInfo.getContent(), KeyName.class);
+ }
+
+ public static X509Data getX509Data(KeyInfo keyInfo) {
+ return getContent(keyInfo.getContent(), X509Data.class);
+ }
+
+ public static X509Certificate getX509Certificate(KeyInfo keyInfo) {
+ X509Data d = getX509Data(keyInfo);
+ return d == null ? null : getContent(d.getContent(), X509Certificate.class);
+ }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
index ca0316b0e28..06d6042fded 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
@@ -58,6 +58,8 @@ public class SAMLArtifactResolveParser extends SAMLRequestAbstractParser impleme
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
index 5f98403dcc2..9d3686bb17f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
@@ -68,6 +68,9 @@ public class SAMLArtifactResponseParser extends SAMLStatusResponseTypeParser imp
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
response.setSignature(sig);
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.AUTHN_REQUEST.get().equals(elementName)) {
SAMLAuthNRequestParser authnParser = new SAMLAuthNRequestParser();
AuthnRequestType authn = (AuthnRequestType) authnParser.parse(xmlEventReader);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
index 139e4e08036..6102e9eb1ea 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
@@ -60,6 +60,8 @@ public class SAMLAttributeQueryParser extends SAMLRequestAbstractParser implemen
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
index 5a15b2cabfb..f1d3349ca2d 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
@@ -76,6 +76,8 @@ public class SAMLAuthNRequestParser extends SAMLRequestAbstractParser implements
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
new file mode 100644
index 00000000000..5f7ebbb9091
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
@@ -0,0 +1,82 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.common.PicketLinkLogger;
+import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.ParserNamespaceSupport;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * Parses <samlp:Extensions> SAML2 element into series of DOM nodes.
+ *
+ * @author hmlnarik
+ */
+public class SAMLExtensionsParser implements ParserNamespaceSupport {
+
+ private static final String EXTENSIONS = JBossSAMLConstants.EXTENSIONS.get();
+
+ private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+ @Override
+ public ExtensionsType parse(XMLEventReader xmlEventReader) throws ParsingException {
+ // Get the startelement
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, EXTENSIONS);
+
+ ExtensionsType extensions = new ExtensionsType();
+
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) xmlEvent;
+ if (StaxParserUtil.matches(endElement, EXTENSIONS)) {
+ endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
+ break;
+ } else
+ throw logger.parserUnknownEndElement(StaxParserUtil.getEndElementName(endElement));
+ }
+
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+
+ extensions.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
+ }
+
+ return extensions;
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ String nsURI = qname.getNamespaceURI();
+ String localPart = qname.getLocalPart();
+
+ return nsURI.equals(JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+ && localPart.equals(JBossSAMLConstants.EXTENSIONS.get());
+ }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
index a2691c24626..92eaf902285 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
@@ -71,6 +71,9 @@ public class SAMLResponseParser extends SAMLStatusResponseTypeParser implements
} else if (JBossSAMLConstants.ASSERTION.get().equals(elementName)) {
SAMLAssertionParser assertionParser = new SAMLAssertionParser();
response.addAssertion(new RTChoiceType((AssertionType) assertionParser.parse(xmlEventReader)));
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
response.setStatus(parseStatus(xmlEventReader));
} else if (JBossSAMLConstants.ENCRYPTED_ASSERTION.get().equals(elementName)) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
index f604cf54637..22ed3832a8c 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
@@ -74,6 +74,8 @@ public class SAMLSloRequestParser extends SAMLRequestAbstractParser implements P
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw logger.parserUnknownTag(elementName, startElement.getLocation());
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
index 167a3c5da57..c0f473b1ef2 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
@@ -60,6 +60,9 @@ public class SAMLSloResponseParser extends SAMLStatusResponseTypeParser implemen
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.bypassElementBlock(xmlEventReader, JBossSAMLConstants.SIGNATURE.get());
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
response.setStatus(parseStatus(xmlEventReader));
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 67fb78f5a7b..ed941a09560 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -62,6 +62,7 @@ import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import org.keycloak.rotation.HardcodedKeyLocator;
/**
* Utility to deal with assertions
@@ -276,7 +277,7 @@ public class AssertionUtil {
Node n = doc.importNode(assertionElement, true);
doc.appendChild(n);
- return new SAML2Signature().validate(doc, publicKey);
+ return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
} catch (Exception e) {
logger.signatureAssertionValidationError(e);
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
index 4c041d11c1f..068c91a051f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
@@ -43,8 +43,12 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
+import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
+import org.w3c.dom.Node;
/**
* Base Class for the Stax writers for SAML
@@ -244,6 +248,28 @@ public class BaseWriter {
StaxUtil.flush(writer);
}
+ public void write(ExtensionsType extensions) throws ProcessingException {
+ if (extensions.getAny().isEmpty()) {
+ return;
+ }
+
+ StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.EXTENSIONS.get(), PROTOCOL_NSURI.get());
+
+ for (Object o : extensions.getAny()) {
+ if (o instanceof Node) {
+ StaxUtil.writeDOMNode(writer, (Node) o);
+ } else if (o instanceof SamlProtocolExtensionsAwareBuilder.NodeGenerator) {
+ SamlProtocolExtensionsAwareBuilder.NodeGenerator ng = (SamlProtocolExtensionsAwareBuilder.NodeGenerator) o;
+ ng.write(writer);
+ } else {
+ throw logger.samlExtensionUnknownChild(o == null ? null : o.getClass());
+ }
+ }
+
+ StaxUtil.writeEndElement(writer);
+ StaxUtil.flush(writer);
+ }
+
private void write(SubjectConfirmationType subjectConfirmationType) throws ProcessingException {
StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.SUBJECT_CONFIRMATION.get(),
ASSERTION_NSURI.get());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
index 9f99780b014..8c115f51744 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
@@ -36,6 +36,7 @@ import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamWriter;
import java.net.URI;
import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
@@ -122,6 +123,11 @@ public class SAMLRequestWriter extends BaseWriter {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
NameIDPolicyType nameIDPolicy = request.getNameIDPolicy();
if (nameIDPolicy != null) {
write(nameIDPolicy);
@@ -171,6 +177,11 @@ public class SAMLRequestWriter extends BaseWriter {
StaxUtil.writeDOMElement(writer, signature);
}
+ ExtensionsType extensions = logOutRequest.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
NameIDType nameID = logOutRequest.getNameID();
if (nameID != null) {
write(nameID, new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.NAMEID.get(), ASSERTION_PREFIX));
@@ -278,6 +289,11 @@ public class SAMLRequestWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
String artifact = request.getArtifact();
if (StringUtil.isNotNull(artifact)) {
StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.ARTIFACT.get(), PROTOCOL_NSURI.get());
@@ -315,6 +331,10 @@ public class SAMLRequestWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
SubjectType subject = request.getSubject();
if (subject != null) {
write(subject);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
index 07fae2a5c04..9327a736511 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
@@ -37,6 +37,7 @@ import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamWriter;
import java.net.URI;
import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* Write a SAML Response to stream
@@ -78,6 +79,10 @@ public class SAMLResponseWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
StatusType status = response.getStatus();
write(status);
@@ -119,6 +124,10 @@ public class SAMLResponseWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
StatusType status = response.getStatus();
if (status != null) {
@@ -163,6 +172,15 @@ public class SAMLResponseWriter extends BaseWriter {
NameIDType issuer = response.getIssuer();
write(issuer, new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ISSUER.get(), ASSERTION_PREFIX));
+ Element sig = response.getSignature();
+ if (sig != null) {
+ StaxUtil.writeDOMElement(writer, sig);
+ }
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
StatusType status = response.getStatus();
write(status);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
new file mode 100644
index 00000000000..1bb90ea9b4f
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.saml.processing.core.util;
+
+import java.util.Objects;
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.StaxUtil;
+import org.w3c.dom.Element;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KeycloakKeySamlExtensionGenerator implements SamlProtocolExtensionsAwareBuilder.NodeGenerator {
+
+ public static final String NS_URI = "urn:keycloak:ext:key:1.0";
+
+ public static final String NS_PREFIX = "kckey";
+
+ public static final String KC_KEY_INFO_ELEMENT_NAME = "KeyInfo";
+
+ public static final String KEY_ID_ATTRIBUTE_NAME = "MessageSigningKeyId";
+
+ private final String keyId;
+
+ public KeycloakKeySamlExtensionGenerator(String keyId) {
+ this.keyId = keyId;
+ }
+
+ @Override
+ public void write(XMLStreamWriter writer) throws ProcessingException {
+ StaxUtil.writeStartElement(writer, NS_PREFIX, KC_KEY_INFO_ELEMENT_NAME, NS_URI);
+ StaxUtil.writeNameSpace(writer, NS_PREFIX, NS_URI);
+ if (this.keyId != null) {
+ StaxUtil.writeAttribute(writer, KEY_ID_ATTRIBUTE_NAME, this.keyId);
+ }
+ StaxUtil.writeEndElement(writer);
+ StaxUtil.flush(writer);
+ }
+
+ /**
+ * Checks that the given element is indeed a Keycloak extension {@code KeyInfo} element and
+ * returns a content of {@code MessageSigningKeyId} attribute in the given element.
+ * @param element Element to obtain the key info from.
+ * @return {@code null} if the element is unknown or there is {@code MessageSigningKeyId} attribute unset,
+ * value of the {@code MessageSigningKeyId} attribute otherwise.
+ */
+ public static String getMessageSigningKeyIdFromElement(Element element) {
+ if (Objects.equals(element.getNamespaceURI(), NS_URI) &&
+ Objects.equals(element.getLocalName(), KC_KEY_INFO_ELEMENT_NAME) &&
+ element.hasAttribute(KEY_ID_ATTRIBUTE_NAME)) {
+ return element.getAttribute(KEY_ID_ATTRIBUTE_NAME);
+ }
+
+ return null;
+ }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
index 19924e9a762..f8181fe2223 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
@@ -32,6 +32,9 @@ public class SignatureUtilTransferObject {
private X509Certificate x509Certificate;
private Document documentToBeSigned;
+
+ private String keyId;
+
private KeyPair keyPair;
private Node nextSibling;
@@ -111,4 +114,12 @@ public class SignatureUtilTransferObject {
public void setX509Certificate(X509Certificate x509Certificate) {
this.x509Certificate = x509Certificate;
}
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(String keyId) {
+ this.keyId = keyId;
+ }
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
index e767d2911a0..245cff957a0 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
@@ -20,12 +20,14 @@ import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
+
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.common.util.StringUtil;
+
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -37,6 +39,7 @@ import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
+import java.util.Objects;
/**
* Utility for XML Encryption Note: This utility is currently using Apache XML Security library API. JSR-106 is
@@ -69,6 +72,10 @@ public class XMLEncryptionUtil {
private static HashMap algorithms = new HashMap(4);
+ private static final String RSA_ENCRYPTION_SCHEME = Objects.equals(System.getProperty("keycloak.saml.key_trans.rsa_v1.5"), "true")
+ ? XMLCipher.RSA_v1dot5
+ : XMLCipher.RSA_OAEP;
+
private static class EncryptionAlgorithm {
EncryptionAlgorithm(String jceName, String xmlSecName, int size) {
@@ -514,7 +521,7 @@ public class XMLEncryptionUtil {
}
}
if (publicKeyAlgo.contains("RSA"))
- return XMLCipher.RSA_v1dot5;
+ return RSA_ENCRYPTION_SCHEME;
if (publicKeyAlgo.contains("DES"))
return XMLCipher.TRIPLEDES_KeyWrap;
throw logger.unsupportedType("unsupported publicKey Algo:" + publicKeyAlgo);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 98635b7a5ab..193af19dffb 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -54,8 +54,6 @@ import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
-import javax.xml.crypto.dsig.keyinfo.KeyValue;
-import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.namespace.QName;
@@ -69,6 +67,7 @@ import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyException;
+import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
@@ -79,7 +78,16 @@ import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
+import javax.xml.crypto.AlgorithmMethod;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.KeySelectorException;
+import javax.xml.crypto.KeySelectorResult;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.api.util.KeyInfoTools;
/**
* Utility for XML Signature Note: You can change the canonicalization method type by using the system property
@@ -105,15 +113,66 @@ public class XMLSignatureUtil {
;
- private static String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
-
- private static XMLSignatureFactory fac = getXMLSignatureFactory();
+ private static final XMLSignatureFactory fac = getXMLSignatureFactory();
/**
* By default, we include the keyinfo in the signature
*/
private static boolean includeKeyInfoInSignature = true;
+ private static class KeySelectorUtilizingKeyNameHint extends KeySelector {
+
+ private final KeyLocator locator;
+
+ private boolean keyLocated = false;
+
+ private String keyName = null;
+
+ public KeySelectorUtilizingKeyNameHint(KeyLocator locator) {
+ this.locator = locator;
+ }
+
+ @Override
+ public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
+ try {
+ KeyName keyNameEl = KeyInfoTools.getKeyName(keyInfo);
+ this.keyName = keyNameEl == null ? null : keyNameEl.getName();
+ final Key key = locator.getKey(keyName);
+ this.keyLocated = key != null;
+ return new KeySelectorResult() {
+ @Override public Key getKey() {
+ return key;
+ }
+ };
+ } catch (KeyManagementException ex) {
+ throw new KeySelectorException(ex);
+ }
+
+ }
+
+ private boolean wasKeyLocated() {
+ return this.keyLocated;
+ }
+ }
+
+ private static class KeySelectorPresetKey extends KeySelector {
+
+ private final Key key;
+
+ public KeySelectorPresetKey(Key key) {
+ this.key = key;
+ }
+
+ @Override
+ public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) {
+ return new KeySelectorResult() {
+ @Override public Key getKey() {
+ return key;
+ }
+ };
+ }
+ }
+
private static XMLSignatureFactory getXMLSignatureFactory() {
XMLSignatureFactory xsf = null;
@@ -157,7 +216,7 @@ public class XMLSignatureUtil {
* @throws MarshalException
* @throws GeneralSecurityException
*/
- public static Document sign(Document doc, Node nodeToBeSigned, KeyPair keyPair, String digestMethod,
+ public static Document sign(Document doc, Node nodeToBeSigned, String keyId, KeyPair keyPair, String digestMethod,
String signatureMethod, String referenceURI, X509Certificate x509Certificate,
String canonicalizationMethodType) throws ParserConfigurationException, GeneralSecurityException,
MarshalException, XMLSignatureException {
@@ -179,7 +238,7 @@ public class XMLSignatureUtil {
if (!referenceURI.isEmpty()) {
propagateIDAttributeSetup(nodeToBeSigned, newDoc.getDocumentElement());
}
- newDoc = sign(newDoc, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
+ newDoc = sign(newDoc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
// if the signed element is a SAMLv2.0 assertion we need to move the signature element to the position
// specified in the schema (before the assertion subject element).
@@ -220,10 +279,10 @@ public class XMLSignatureUtil {
* @throws MarshalException
* @throws XMLSignatureException
*/
- public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+ public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
String signatureMethod, String referenceURI, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException {
- sign(elementToSign, nextSibling, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+ sign(elementToSign, nextSibling, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
}
/**
@@ -242,7 +301,7 @@ public class XMLSignatureUtil {
* @throws XMLSignatureException
* @since 2.5.0
*/
- public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+ public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
String signatureMethod, String referenceURI, X509Certificate x509Certificate, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException {
PrivateKey signingKey = keyPair.getPrivate();
@@ -250,7 +309,7 @@ public class XMLSignatureUtil {
DOMSignContext dsc = new DOMSignContext(signingKey, elementToSign, nextSibling);
- signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+ signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
}
/**
@@ -284,9 +343,9 @@ public class XMLSignatureUtil {
* @throws XMLSignatureException
* @throws MarshalException
*/
- public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
+ public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException {
- return sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+ return sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
}
/**
@@ -304,7 +363,7 @@ public class XMLSignatureUtil {
* @throws MarshalException
* @since 2.5.0
*/
- public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
+ public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
X509Certificate x509Certificate, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException {
logger.trace("Document to be signed=" + DocumentUtil.asString(doc));
@@ -313,7 +372,7 @@ public class XMLSignatureUtil {
DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement());
- signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+ signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
return doc;
}
@@ -331,6 +390,7 @@ public class XMLSignatureUtil {
public static Document sign(SignatureUtilTransferObject dto, String canonicalizationMethodType) throws GeneralSecurityException, MarshalException,
XMLSignatureException {
Document doc = dto.getDocumentToBeSigned();
+ String keyId = dto.getKeyId();
KeyPair keyPair = dto.getKeyPair();
Node nextSibling = dto.getNextSibling();
String digestMethod = dto.getDigestMethod();
@@ -344,13 +404,14 @@ public class XMLSignatureUtil {
DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement(), nextSibling);
- signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
+ signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
return doc;
}
/**
- * Validate a signed document with the given public key
+ * Validate a signed document with the given public key. All elements that contain a Signature are checked,
+ * this way both assertions and the containing document are verified when signed.
*
* @param signedDoc
* @param publicKey
@@ -361,7 +422,7 @@ public class XMLSignatureUtil {
* @throws XMLSignatureException
*/
@SuppressWarnings("unchecked")
- public static boolean validate(Document signedDoc, Key publicKey) throws MarshalException, XMLSignatureException {
+ public static boolean validate(Document signedDoc, final KeyLocator locator) throws MarshalException, XMLSignatureException {
if (signedDoc == null)
throw logger.nullArgumentError("Signed Document");
@@ -374,7 +435,7 @@ public class XMLSignatureUtil {
return false;
}
- if (publicKey == null)
+ if (locator == null)
throw logger.nullValueError("Public Key");
int signedAssertions = 0;
@@ -390,24 +451,7 @@ public class XMLSignatureUtil {
}
}
- DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(i));
- XMLSignature signature = fac.unmarshalXMLSignature(valContext);
-
- boolean coreValidity = signature.validate(valContext);
-
- if (!coreValidity) {
- if (logger.isTraceEnabled()) {
- boolean sv = signature.getSignatureValue().validate(valContext);
- logger.trace("Signature validation status: " + sv);
-
- List references = signature.getSignedInfo().getReferences();
- for (Reference ref : references) {
- logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
- }
- }
-
- return false;
- }
+ if (! validateSingleNode(signatureNode, locator)) return false;
}
NodeList assertions = signedDoc.getElementsByTagNameNS(assertionNameSpaceUri, JBossSAMLConstants.ASSERTION.get());
@@ -423,6 +467,62 @@ public class XMLSignatureUtil {
return true;
}
+ private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+ KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
+ try {
+ if (validateUsingKeySelector(signatureNode, sel)) {
+ return true;
+ }
+ if (sel.wasKeyLocated()) {
+ return false;
+ }
+ } catch (XMLSignatureException ex) { // pass through MarshalException
+ logger.debug("Verification failed for key " + sel.keyName + ": " + ex);
+ logger.trace(ex);
+ }
+
+ logger.trace("Could not validate signature using ds:KeyInfo/ds:KeyName hint.");
+
+ if (locator instanceof Iterable) {
+ Iterable availableKeys = (Iterable) locator;
+
+ logger.trace("Trying hard to validate XML signature using all available keys.");
+
+ for (Key key : availableKeys) {
+ try {
+ if (validateUsingKeySelector(signatureNode, new KeySelectorPresetKey(key))) {
+ return true;
+ }
+ } catch (XMLSignatureException ex) { // pass through MarshalException
+ logger.debug("Verification failed: " + ex);
+ logger.trace(ex);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean validateUsingKeySelector(Node signatureNode, KeySelector validationKeySelector) throws XMLSignatureException, MarshalException {
+ DOMValidateContext valContext = new DOMValidateContext(validationKeySelector, signatureNode);
+ XMLSignature signature = fac.unmarshalXMLSignature(valContext);
+ boolean coreValidity = signature.validate(valContext);
+
+ if (! coreValidity) {
+ if (logger.isTraceEnabled()) {
+ boolean sv = signature.getSignatureValue().validate(valContext);
+ logger.trace("Signature validation status: " + sv);
+
+ List references = signature.getSignedInfo().getReferences();
+ for (Reference ref : references) {
+ logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
+ }
+ }
+ }
+
+ return coreValidity;
+ }
+
/**
* Marshall a SignatureType to output stream
*
@@ -594,7 +694,7 @@ public class XMLSignatureUtil {
throw logger.unsupportedType(key.toString());
}
- private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, PublicKey publicKey,
+ private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, String keyId, PublicKey publicKey,
X509Certificate x509Certificate, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException {
dsc.setDefaultNamespacePrefix("dsig");
@@ -603,7 +703,7 @@ public class XMLSignatureUtil {
Transform transform1 = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
Transform transform2 = fac.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec) null);
- List transformList = new ArrayList();
+ List transformList = new ArrayList<>();
transformList.add(transform1);
transformList.add(transform2);
@@ -616,37 +716,34 @@ public class XMLSignatureUtil {
SignatureMethod signatureMethodObj = fac.newSignatureMethod(signatureMethod, null);
SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethodObj, referenceList);
- KeyInfo ki = null;
+ KeyInfo ki;
if (includeKeyInfoInSignature) {
- ki = createKeyInfo(publicKey, x509Certificate);
+ ki = createKeyInfo(keyId, publicKey, x509Certificate);
+ } else {
+ ki = createKeyInfo(keyId, null, null);
}
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
}
- private static KeyInfo createKeyInfo(PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
+ private static KeyInfo createKeyInfo(String keyId, PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory();
- KeyInfo keyInfo = null;
- KeyValue keyValue = null;
- //Just with public key
- if (publicKey != null) {
- keyValue = keyInfoFactory.newKeyValue(publicKey);
- keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(keyValue));
+
+ List items = new LinkedList<>();
+
+ if (keyId != null) {
+ items.add(keyInfoFactory.newKeyName(keyId));
}
+
if (x509Certificate != null) {
- List x509list = new ArrayList();
-
- x509list.add(x509Certificate);
- X509Data x509Data = keyInfoFactory.newX509Data(x509list);
- List items = new ArrayList();
-
- items.add(x509Data);
- if (keyValue != null) {
- items.add(keyValue);
- }
- keyInfo = keyInfoFactory.newKeyInfo(items);
+ items.add(keyInfoFactory.newX509Data(Collections.singletonList(x509Certificate)));
}
- return keyInfo;
+
+ if (publicKey != null) {
+ items.add(keyInfoFactory.newKeyValue(publicKey));
+ }
+
+ return keyInfoFactory.newKeyInfo(items);
}
}
\ No newline at end of file
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
new file mode 100644
index 00000000000..ad150e96c5f
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import java.io.InputStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.w3c.dom.Element;
+
+/**
+ * Test class for SAML parser.
+ *
+ * TODO: Add further tests.
+ *
+ * @author hmlnarik
+ */
+public class SAMLParserTest {
+
+ @Test
+ public void testSaml20EncryptedAssertionsSignedReceivedWithRedirectBinding() throws Exception {
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+
+ ResponseType resp = (ResponseType) parsedObject;
+ assertThat(resp.getSignature(), nullValue());
+ assertThat(resp.getConsent(), nullValue());
+ assertThat(resp.getIssuer(), not(nullValue()));
+ assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+ assertThat(resp.getExtensions(), not(nullValue()));
+ assertThat(resp.getExtensions().getAny().size(), is(1));
+ assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+ Element el = (Element) resp.getExtensions().getAny().get(0);
+ assertThat(el.getLocalName(), is("KeyInfo"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+ assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+ assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+
+ assertThat(resp.getAssertions(), not(nullValue()));
+ assertThat(resp.getAssertions().size(), is(1));
+ }
+
+ @Test
+ public void testSaml20EncryptedAssertionsSignedTwoExtensionsReceivedWithRedirectBinding() throws Exception {
+ Element el;
+
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response-two-extensions.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+
+ ResponseType resp = (ResponseType) parsedObject;
+ assertThat(resp.getSignature(), nullValue());
+ assertThat(resp.getConsent(), nullValue());
+ assertThat(resp.getIssuer(), not(nullValue()));
+ assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+ assertThat(resp.getExtensions(), not(nullValue()));
+ assertThat(resp.getExtensions().getAny().size(), is(2));
+ assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+ el = (Element) resp.getExtensions().getAny().get(0);
+ assertThat(el.getLocalName(), is("KeyInfo"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+ assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+ assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+ assertThat(resp.getExtensions().getAny().get(1), instanceOf(Element.class));
+ el = (Element) resp.getExtensions().getAny().get(1);
+ assertThat(el.getLocalName(), is("ever"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:what:1.0"));
+ assertThat(el.hasAttribute("what"), is(true));
+ assertThat(el.getAttribute("what"), is("ever"));
+
+ assertThat(resp.getAssertions(), not(nullValue()));
+ assertThat(resp.getAssertions().size(), is(1));
+ }
+
+ @Test
+ public void testSaml20PostLogoutRequest() throws Exception {
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-signed-logout-request.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(LogoutRequestType.class));
+
+ }
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
new file mode 100644
index 00000000000..94a6fdb3750
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
@@ -0,0 +1,30 @@
+
+ http://localhost:8081/auth/realms/saml-demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+
+
+
+
+
+
+ RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+
+
+
+
+
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
new file mode 100644
index 00000000000..d8d4c151a3c
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
@@ -0,0 +1,29 @@
+
+ http://localhost:8081/auth/realms/saml-demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+
+
+
+
+
+
+ RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+
+
+
+
+
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
new file mode 100644
index 00000000000..8c4ab20704f
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
@@ -0,0 +1,32 @@
+
+ http://localhost:8080/sales-post-enc/
+
+
+
+
+
+
+
+
+
+
+ zeWNo5eav5tFOOCEJ1YU9eINkPnBSfixzAr8AOC4R4c=
+
+
+
+ pyOiS1LsV/XR08zhcN6IqSYuKTDln4otmCvZxCc07ORP1C9jragu8V8rEE09qt/zBcdw7Arb8eLNNC6oCnrnMxuvzRInVTwt7T5K3t0UlzRWOb3HMElhcWFEgDzh6uKw5Cr45A01XNpojtJWCML/qU2Enyyy80FBlCJNcbzyLxE=
+
+
+
+
+
+ 2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEik=
+
+ AQAB
+
+
+
+
+ bburke
+ a3b2df1c-1095-487b-8b56-f62818c449e3
+
diff --git a/server-spi-private/pom.xml b/server-spi-private/pom.xml
new file mode 100755
index 00000000000..a1d3df23ffd
--- /dev/null
+++ b/server-spi-private/pom.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+ keycloak-parent
+ org.keycloak
+ 2.4.0.CR1-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ keycloak-server-spi-private
+ Keycloak Server Private SPI
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+ org.keycloak
+ keycloak-server-spi
+ provided
+
+
+ org.jboss.spec.javax.transaction
+ jboss-transaction-api_1.2_spec
+ provided
+
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ provided
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ provided
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ provided
+
+
+ org.keycloak
+ keycloak-core
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+
+
diff --git a/server-spi/src/main/java/org/keycloak/ServerStartupError.java b/server-spi-private/src/main/java/org/keycloak/ServerStartupError.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/ServerStartupError.java
rename to server-spi-private/src/main/java/org/keycloak/ServerStartupError.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AbstractAuthenticationFlowContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/AbstractAuthenticationFlowContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AbstractAuthenticationFlowContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AbstractAuthenticationFlowContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlow.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlow.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlow.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlow.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/Authenticator.java b/server-spi-private/src/main/java/org/keycloak/authentication/Authenticator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/Authenticator.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/Authenticator.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticator.java b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticatorSpi.java b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ClientAuthenticatorSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FlowStatus.java b/server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FlowStatus.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ForkFlowException.java b/server-spi-private/src/main/java/org/keycloak/authentication/ForkFlowException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ForkFlowException.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ForkFlowException.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormAction.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormAction.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormAction.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormAction.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormActionFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormActionFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormActionFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormActionFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormActionSpi.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormActionSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormActionSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormActionSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormAuthenticator.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormAuthenticator.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticator.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/FormContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/FormContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/RequiredActionContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/RequiredActionFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/RequiredActionSpi.java b/server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/RequiredActionSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authentication/ValidationContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/ValidationContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authentication/ValidationContext.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ValidationContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationSpi.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/AuthorizationSpi.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/Decision.java b/server-spi-private/src/main/java/org/keycloak/authorization/Decision.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/Decision.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/Decision.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/attribute/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/attribute/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java b/server-spi-private/src/main/java/org/keycloak/authorization/identity/Identity.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/identity/Identity.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/identity/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/identity/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/identity/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Policy.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/model/Policy.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Resource.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/model/Resource.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/ResourceServer.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/model/ResourceServer.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Scope.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/model/Scope.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/model/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/model/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/policy/provider/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/StoreFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/StoreFactory.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/StoreFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/package-info.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/package-info.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/package-info.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/package-info.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java
rename to server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/ConfigConstants.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/ConfigConstants.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/ConfigConstants.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/ConfigConstants.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityBrokerException.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityBrokerException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityBrokerException.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityBrokerException.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderMapperSpi.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderMapperSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderMapperSpi.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderMapperSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderSpi.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderSpi.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProviderSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
rename to server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java
rename to server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/social/SocialIdentityProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/social/SocialIdentityProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/broker/social/SocialIdentityProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/broker/social/SocialProviderSpi.java b/server-spi-private/src/main/java/org/keycloak/broker/social/SocialProviderSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/broker/social/SocialProviderSpi.java
rename to server-spi-private/src/main/java/org/keycloak/broker/social/SocialProviderSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ClusterEvent.java b/server-spi-private/src/main/java/org/keycloak/cluster/ClusterEvent.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ClusterEvent.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ClusterEvent.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ClusterListener.java b/server-spi-private/src/main/java/org/keycloak/cluster/ClusterListener.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ClusterListener.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ClusterListener.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ClusterProvider.java b/server-spi-private/src/main/java/org/keycloak/cluster/ClusterProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ClusterProvider.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ClusterProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ClusterProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/cluster/ClusterProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ClusterProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ClusterProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ClusterSpi.java b/server-spi-private/src/main/java/org/keycloak/cluster/ClusterSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ClusterSpi.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ClusterSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/cluster/ExecutionResult.java b/server-spi-private/src/main/java/org/keycloak/cluster/ExecutionResult.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/cluster/ExecutionResult.java
rename to server-spi-private/src/main/java/org/keycloak/cluster/ExecutionResult.java
diff --git a/server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java b/server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java
rename to server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java b/server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java
rename to server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java b/server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java
rename to server-spi-private/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailException.java b/server-spi-private/src/main/java/org/keycloak/email/EmailException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailException.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailException.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailSenderProvider.java b/server-spi-private/src/main/java/org/keycloak/email/EmailSenderProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailSenderProvider.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailSenderProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailSenderProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/email/EmailSenderProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailSenderProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailSenderProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailSenderSpi.java b/server-spi-private/src/main/java/org/keycloak/email/EmailSenderSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailSenderSpi.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailSenderSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailTemplateProvider.java b/server-spi-private/src/main/java/org/keycloak/email/EmailTemplateProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailTemplateProvider.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailTemplateProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailTemplateProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/email/EmailTemplateProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailTemplateProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailTemplateProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/email/EmailTemplateSpi.java b/server-spi-private/src/main/java/org/keycloak/email/EmailTemplateSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/email/EmailTemplateSpi.java
rename to server-spi-private/src/main/java/org/keycloak/email/EmailTemplateSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/events/Details.java b/server-spi-private/src/main/java/org/keycloak/events/Details.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/Details.java
rename to server-spi-private/src/main/java/org/keycloak/events/Details.java
diff --git a/server-spi/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/Errors.java
rename to server-spi-private/src/main/java/org/keycloak/events/Errors.java
diff --git a/server-spi/src/main/java/org/keycloak/events/Event.java b/server-spi-private/src/main/java/org/keycloak/events/Event.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/Event.java
rename to server-spi-private/src/main/java/org/keycloak/events/Event.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventBuilder.java b/server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventBuilder.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventBuilder.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventListenerProvider.java b/server-spi-private/src/main/java/org/keycloak/events/EventListenerProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventListenerProvider.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventListenerProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventListenerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/events/EventListenerProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventListenerProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventListenerProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventListenerSpi.java b/server-spi-private/src/main/java/org/keycloak/events/EventListenerSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventListenerSpi.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventListenerSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventQuery.java b/server-spi-private/src/main/java/org/keycloak/events/EventQuery.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventQuery.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventQuery.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/events/EventStoreProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventStoreProvider.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventStoreProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/events/EventStoreProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventStoreProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventStoreProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/events/EventStoreSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventStoreSpi.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventStoreSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/events/EventType.java b/server-spi-private/src/main/java/org/keycloak/events/EventType.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/EventType.java
rename to server-spi-private/src/main/java/org/keycloak/events/EventType.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/AdminEvent.java b/server-spi-private/src/main/java/org/keycloak/events/admin/AdminEvent.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/AdminEvent.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/AdminEvent.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/AdminEventQuery.java b/server-spi-private/src/main/java/org/keycloak/events/admin/AdminEventQuery.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/AdminEventQuery.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/AdminEventQuery.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/AuthDetails.java b/server-spi-private/src/main/java/org/keycloak/events/admin/AuthDetails.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/AuthDetails.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/AuthDetails.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/AuthQuery.java b/server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/AuthQuery.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/AuthQuery.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/OperationType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/OperationType.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/OperationType.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/OperationType.java
diff --git a/server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java
rename to server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ExportProvider.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ExportProvider.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ExportProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ExportSpi.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ExportSpi.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ExportSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ImportProvider.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ImportProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ImportProvider.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ImportProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/ImportSpi.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ImportSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/ImportSpi.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/ImportSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/Strategy.java b/server-spi-private/src/main/java/org/keycloak/exportimport/Strategy.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/Strategy.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/Strategy.java
diff --git a/server-spi/src/main/java/org/keycloak/exportimport/UsersExportStrategy.java b/server-spi-private/src/main/java/org/keycloak/exportimport/UsersExportStrategy.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/exportimport/UsersExportStrategy.java
rename to server-spi-private/src/main/java/org/keycloak/exportimport/UsersExportStrategy.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/account/AccountPages.java b/server-spi-private/src/main/java/org/keycloak/forms/account/AccountPages.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/account/AccountPages.java
rename to server-spi-private/src/main/java/org/keycloak/forms/account/AccountPages.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/account/AccountProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/account/AccountProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/account/AccountProvider.java
rename to server-spi-private/src/main/java/org/keycloak/forms/account/AccountProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/account/AccountProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/forms/account/AccountProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/account/AccountProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/forms/account/AccountProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/account/AccountSpi.java b/server-spi-private/src/main/java/org/keycloak/forms/account/AccountSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/account/AccountSpi.java
rename to server-spi-private/src/main/java/org/keycloak/forms/account/AccountSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/login/LoginFormsPages.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/login/LoginFormsPages.java
rename to server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
rename to server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/login/LoginFormsProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/login/LoginFormsProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/forms/login/LoginFormsSpi.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/forms/login/LoginFormsSpi.java
rename to server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
similarity index 97%
rename from server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
index 31ca54194fe..5eebe818ebd 100644
--- a/server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
@@ -21,7 +21,6 @@ import org.keycloak.provider.Provider;
import java.security.PrivateKey;
import java.security.PublicKey;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/keys/KeyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/KeyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/keys/KeyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeySpi.java b/server-spi-private/src/main/java/org/keycloak/keys/KeySpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/KeySpi.java
rename to server-spi-private/src/main/java/org/keycloak/keys/KeySpi.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/PublicKeyLoader.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/PublicKeyLoader.java
rename to server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
rename to server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageSpi.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/keys/PublicKeyStorageSpi.java
rename to server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java b/server-spi-private/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
rename to server-spi-private/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapper.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
rename to server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapper.java
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
rename to server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java b/server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
rename to server-spi-private/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
rename to server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationProvider.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/MigrationProvider.java
rename to server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/MigrationProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/migration/MigrationProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationSpi.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/MigrationSpi.java
rename to server-spi-private/src/main/java/org/keycloak/migration/MigrationSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java b/server-spi-private/src/main/java/org/keycloak/migration/ModelVersion.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/ModelVersion.java
rename to server-spi-private/src/main/java/org/keycloak/migration/ModelVersion.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/Migration.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/Migration.java
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java
rename to server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java
diff --git a/server-spi/src/main/java/org/keycloak/models/AccountRoles.java b/server-spi-private/src/main/java/org/keycloak/models/AccountRoles.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/AccountRoles.java
rename to server-spi-private/src/main/java/org/keycloak/models/AccountRoles.java
diff --git a/server-spi/src/main/java/org/keycloak/models/AdminRoles.java b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/AdminRoles.java
rename to server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java
diff --git a/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java b/server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
rename to server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ClaimMask.java b/server-spi-private/src/main/java/org/keycloak/models/ClaimMask.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ClaimMask.java
rename to server-spi-private/src/main/java/org/keycloak/models/ClaimMask.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientConfigResolver.java b/server-spi-private/src/main/java/org/keycloak/models/ClientConfigResolver.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ClientConfigResolver.java
rename to server-spi-private/src/main/java/org/keycloak/models/ClientConfigResolver.java
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/Constants.java
rename to server-spi-private/src/main/java/org/keycloak/models/Constants.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ImpersonationConstants.java b/server-spi-private/src/main/java/org/keycloak/models/ImpersonationConstants.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ImpersonationConstants.java
rename to server-spi-private/src/main/java/org/keycloak/models/ImpersonationConstants.java
diff --git a/server-spi/src/main/java/org/keycloak/models/LDAPConstants.java b/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/LDAPConstants.java
rename to server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ModelDuplicateException.java b/server-spi-private/src/main/java/org/keycloak/models/ModelDuplicateException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ModelDuplicateException.java
rename to server-spi-private/src/main/java/org/keycloak/models/ModelDuplicateException.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ModelException.java b/server-spi-private/src/main/java/org/keycloak/models/ModelException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ModelException.java
rename to server-spi-private/src/main/java/org/keycloak/models/ModelException.java
diff --git a/server-spi/src/main/java/org/keycloak/models/ModelReadOnlyException.java b/server-spi-private/src/main/java/org/keycloak/models/ModelReadOnlyException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/ModelReadOnlyException.java
rename to server-spi-private/src/main/java/org/keycloak/models/ModelReadOnlyException.java
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/RealmProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/RealmProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/RealmProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmSpi.java b/server-spi-private/src/main/java/org/keycloak/models/RealmSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/RealmSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/RealmSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java b/server-spi-private/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationSpi.java b/server-spi-private/src/main/java/org/keycloak/models/UserFederationSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserFederationSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserFederationSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/UserProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionSpi.java b/server-spi-private/src/main/java/org/keycloak/models/UserSessionSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserSessionSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserSessionSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSpi.java b/server-spi-private/src/main/java/org/keycloak/models/UserSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/UserSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/UserSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProviderSpi.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProviderSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProviderSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProviderSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedRealmModel.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockManager.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockManager.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/dblock/DBLockManager.java
rename to server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockManager.java
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
rename to server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
rename to server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/CredentialValidation.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/CredentialValidation.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FormMessage.java b/server-spi-private/src/main/java/org/keycloak/models/utils/FormMessage.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/FormMessage.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/FormMessage.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
similarity index 90%
rename from server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index b57d2b1e62e..e26523366ee 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -257,75 +257,6 @@ public final class KeycloakModelUtils {
return realmName + "-realm";
}
- /**
- * @param roles
- * @param targetRole
- * @return true if targetRole is in roles (directly or indirectly via composite role)
- */
- public static boolean hasRole(Set roles, RoleModel targetRole) {
- if (roles.contains(targetRole)) return true;
-
- for (RoleModel mapping : roles) {
- if (mapping.hasRole(targetRole)) return true;
- }
- return false;
- }
-
- /**
- * Checks whether the {@code targetRole} is contained in the given group or its parents
- * (if requested)
- * @param group Group to check role for
- * @param targetRole
- * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
- * @return true if targetRole is in roles (directly or indirectly via composite role)
- */
- public static boolean hasRoleFromGroup(GroupModel group, RoleModel targetRole, boolean checkParentGroup) {
- if (group.hasRole(targetRole))
- return true;
-
- if (checkParentGroup) {
- GroupModel parent = group.getParent();
- return parent != null && hasRoleFromGroup(parent, targetRole, true);
- }
-
- return false;
- }
-
- /**
- * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents
- * (if requested)
- * @param groups
- * @param targetRole
- * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
- * @return true if targetRole is in roles (directly or indirectly via composite role)
- */
- public static boolean hasRoleFromGroup(Iterable groups, RoleModel targetRole, boolean checkParentGroup) {
- if (groups == null) {
- return false;
- }
-
- return StreamSupport.stream(groups.spliterator(), false)
- .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
- }
-
- /**
- *
- * @param groups
- * @param targetGroup
- * @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
- */
- public static boolean isMember(Set groups, GroupModel targetGroup) {
- if (groups.contains(targetGroup)) return true;
-
- for (GroupModel mapping : groups) {
- GroupModel child = mapping;
- while(child.getParent() != null) {
- if (child.getParent().equals(targetGroup)) return true;
- child = child.getParent();
- }
- }
- return false;
- }
// USER FEDERATION RELATED STUFF
/**
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java b/server-spi-private/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RealmImporter.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmImporter.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/RealmImporter.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/RealmImporter.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java b/server-spi-private/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java b/server-spi-private/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi-private/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/Properties.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Properties.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/Properties.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Properties.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/Property.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Property.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/Property.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Property.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
rename to server-spi-private/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
similarity index 96%
rename from server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
index 114447834d0..b9f6f4ce414 100644
--- a/server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
@@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -28,7 +29,6 @@ import org.keycloak.models.UserModel;
*/
public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
- public static final String ID = "forceExpiredPasswordChange";
public static final int DEFAULT_VALUE = 365;
@Override
@@ -50,7 +50,7 @@ public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicy
@Override
public String getId() {
- return ID;
+ return PasswordPolicy.FORCE_EXPIRED_ID;
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
similarity index 90%
rename from server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
index e7daaabba0a..303ba7993fa 100644
--- a/server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
@@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -28,10 +29,6 @@ import org.keycloak.models.UserModel;
*/
public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
- public static final String DEFAULT_VALUE = "pbkdf2";
-
- public static final String ID = "hashAlgorithm";
-
@Override
public PasswordPolicyProvider create(KeycloakSession session) {
return this;
@@ -51,7 +48,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
@Override
public String getId() {
- return ID;
+ return PasswordPolicy.HASH_ALGORITHM_ID;
}
@Override
@@ -76,7 +73,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
@Override
public String getDefaultConfigValue() {
- return DEFAULT_VALUE;
+ return PasswordPolicy.HASH_ALGORITHM_DEFAULT;
}
@Override
@@ -86,7 +83,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
@Override
public Object parseConfig(String value) {
- return value != null ? value : DEFAULT_VALUE;
+ return value != null ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
}
}
diff --git a/server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
similarity index 89%
rename from server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
index ae9d497e238..695ab282ea6 100644
--- a/server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
@@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -28,9 +29,6 @@ import org.keycloak.models.UserModel;
*/
public class HashIterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
- public static final int DEFAULT_VALUE = 20000;
-
- public static final String ID = "hashIterations";
@Override
public PasswordPolicyProvider create(KeycloakSession session) {
@@ -47,7 +45,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
@Override
public String getId() {
- return ID;
+ return PasswordPolicy.HASH_ITERATIONS_ID;
}
@Override
@@ -62,7 +60,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
+ return value != null ? Integer.parseInt(value) : PasswordPolicy.HASH_ITERATIONS_DEFAULT;
}
@Override
@@ -77,7 +75,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
@Override
public String getDefaultConfigValue() {
- return String.valueOf(DEFAULT_VALUE);
+ return String.valueOf(PasswordPolicy.HASH_ITERATIONS_DEFAULT);
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
similarity index 98%
rename from server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
index 154ea56b39d..004d54028ed 100644
--- a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
@@ -49,7 +49,7 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
@Override
public PolicyError validate(RealmModel realm, UserModel user, String password) {
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
- int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
+ int passwordHistoryPolicyValue = policy.getPolicyConfig(PasswordPolicy.PASSWORD_HISTORY_ID);
if (passwordHistoryPolicyValue != -1) {
List storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
for (CredentialModel cred : storedPasswords) {
diff --git a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
similarity index 95%
rename from server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
index c2c180a020a..5f8a9d18884 100644
--- a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
@@ -20,18 +20,18 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
/**
* @author Stian Thorgersen
*/
public class HistoryPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
- public static final String ID = "passwordHistory";
public static final Integer DEFAULT_VALUE = 3;
@Override
public String getId() {
- return ID;
+ return PasswordPolicy.PASSWORD_HISTORY_ID;
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
rename to server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
rename to server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
rename to server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/LoginProtocol.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/LoginProtocol.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
rename to server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java b/server-spi-private/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
rename to server-spi-private/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
diff --git a/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderLoader.java b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoader.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/provider/ProviderLoader.java
rename to server-spi-private/src/main/java/org/keycloak/provider/ProviderLoader.java
diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/Script.java b/server-spi-private/src/main/java/org/keycloak/scripting/Script.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/Script.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/Script.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/ScriptExecutionException.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptExecutionException.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/ScriptExecutionException.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/ScriptExecutionException.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/ScriptingProvider.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/ScriptingProvider.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/ScriptingProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/ScriptingProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/scripting/ScriptingSpi.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/scripting/ScriptingSpi.java
rename to server-spi-private/src/main/java/org/keycloak/scripting/ScriptingSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtector.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
rename to server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java
rename to server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java
rename to server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/server-spi-private/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
rename to server-spi-private/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
diff --git a/server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java
rename to server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java
rename to server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java
diff --git a/server-spi/src/main/java/org/keycloak/theme/Theme.java b/server-spi-private/src/main/java/org/keycloak/theme/Theme.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/theme/Theme.java
rename to server-spi-private/src/main/java/org/keycloak/theme/Theme.java
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeProvider.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/theme/ThemeProvider.java
rename to server-spi-private/src/main/java/org/keycloak/theme/ThemeProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/theme/ThemeProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/theme/ThemeProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeSpi.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/theme/ThemeSpi.java
rename to server-spi-private/src/main/java/org/keycloak/theme/ThemeSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/timer/ScheduledTask.java b/server-spi-private/src/main/java/org/keycloak/timer/ScheduledTask.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/timer/ScheduledTask.java
rename to server-spi-private/src/main/java/org/keycloak/timer/ScheduledTask.java
diff --git a/server-spi/src/main/java/org/keycloak/timer/TimerProvider.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/timer/TimerProvider.java
rename to server-spi-private/src/main/java/org/keycloak/timer/TimerProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/timer/TimerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/timer/TimerProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/timer/TimerProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/timer/TimerSpi.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/timer/TimerSpi.java
rename to server-spi-private/src/main/java/org/keycloak/timer/TimerSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java b/server-spi-private/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java
rename to server-spi-private/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java
diff --git a/server-spi/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java b/server-spi-private/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java
rename to server-spi-private/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java
diff --git a/server-spi/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java b/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java
rename to server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java
diff --git a/server-spi/src/main/java/org/keycloak/truststore/TruststoreProvider.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/truststore/TruststoreProvider.java
rename to server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
diff --git a/server-spi/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java
rename to server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java
diff --git a/server-spi/src/main/java/org/keycloak/truststore/TruststoreSpi.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreSpi.java
similarity index 100%
rename from server-spi/src/main/java/org/keycloak/truststore/TruststoreSpi.java
rename to server-spi-private/src/main/java/org/keycloak/truststore/TruststoreSpi.java
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
similarity index 100%
rename from server-spi/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
rename to server-spi-private/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
similarity index 100%
rename from server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
rename to server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
similarity index 100%
rename from server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
rename to server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100755
index 00000000000..bbd588ea50b
--- /dev/null
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1,68 @@
+#
+# 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.
+#
+
+org.keycloak.models.UserFederationSpi
+org.keycloak.storage.UserStorageProviderSpi
+org.keycloak.storage.federated.UserFederatedStorageProviderSpi
+org.keycloak.mappers.UserFederationMapperSpi
+org.keycloak.models.RealmSpi
+org.keycloak.models.UserSessionSpi
+org.keycloak.models.UserSpi
+org.keycloak.models.session.UserSessionPersisterSpi
+org.keycloak.models.dblock.DBLockSpi
+org.keycloak.migration.MigrationSpi
+org.keycloak.events.EventListenerSpi
+org.keycloak.events.EventStoreSpi
+org.keycloak.exportimport.ExportSpi
+org.keycloak.exportimport.ImportSpi
+org.keycloak.timer.TimerSpi
+org.keycloak.scripting.ScriptingSpi
+org.keycloak.services.managers.BruteForceProtectorSpi
+org.keycloak.services.resource.RealmResourceSPI
+org.keycloak.protocol.ClientInstallationSpi
+org.keycloak.protocol.LoginProtocolSpi
+org.keycloak.protocol.ProtocolMapperSpi
+org.keycloak.broker.provider.IdentityProviderSpi
+org.keycloak.broker.provider.IdentityProviderMapperSpi
+org.keycloak.broker.social.SocialProviderSpi
+org.keycloak.forms.account.AccountSpi
+org.keycloak.forms.login.LoginFormsSpi
+org.keycloak.email.EmailSenderSpi
+org.keycloak.email.EmailTemplateSpi
+org.keycloak.theme.ThemeSpi
+org.keycloak.truststore.TruststoreSpi
+org.keycloak.connections.httpclient.HttpClientSpi
+org.keycloak.models.cache.CacheRealmProviderSpi
+org.keycloak.models.cache.CacheUserProviderSpi
+org.keycloak.authentication.AuthenticatorSpi
+org.keycloak.authentication.ClientAuthenticatorSpi
+org.keycloak.authentication.RequiredActionSpi
+org.keycloak.authentication.FormAuthenticatorSpi
+org.keycloak.authentication.FormActionSpi
+org.keycloak.cluster.ClusterSpi
+org.keycloak.authorization.policy.provider.PolicySpi
+org.keycloak.authorization.store.StoreFactorySpi
+org.keycloak.authorization.AuthorizationSpi
+org.keycloak.models.cache.authorization.CachedStoreFactorySpi
+org.keycloak.protocol.oidc.TokenIntrospectionSpi
+org.keycloak.policy.PasswordPolicySpi
+org.keycloak.policy.PasswordPolicyManagerSpi
+org.keycloak.transaction.TransactionManagerLookupSpi
+org.keycloak.credential.hash.PasswordHashSpi
+org.keycloak.credential.CredentialSpi
+org.keycloak.keys.PublicKeyStorageSpi
+org.keycloak.keys.KeySpi
\ No newline at end of file
diff --git a/server-spi/src/test/java/org/keycloak/models/HmacTest.java b/server-spi-private/src/test/java/org/keycloak/models/HmacTest.java
similarity index 100%
rename from server-spi/src/test/java/org/keycloak/models/HmacTest.java
rename to server-spi-private/src/test/java/org/keycloak/models/HmacTest.java
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
index e644a8095c9..be5f55189f9 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -18,7 +18,6 @@
package org.keycloak.models;
import org.keycloak.common.ClientConnection;
-import org.keycloak.models.utils.RealmImporter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
@@ -52,8 +51,6 @@ public interface KeycloakContext {
void setConnection(ClientConnection connection);
- RealmImporter getRealmManager();
-
Locale resolveLocale(UserModel user);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index cb1089192d1..f3242eb73b5 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -20,7 +20,6 @@ package org.keycloak.models;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
-import org.keycloak.scripting.ScriptingProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import java.util.Set;
@@ -157,8 +156,4 @@ public interface KeycloakSession {
*/
KeyManager keys();
- /**
- * Keycloak scripting support.
- */
- ScriptingProvider scripting();
}
diff --git a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
index 95dfc5301e5..8833c8a33c2 100755
--- a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -17,10 +17,6 @@
package org.keycloak.models;
-import org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory;
-import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
-import org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory;
-import org.keycloak.policy.HistoryPasswordPolicyProviderFactory;
import org.keycloak.policy.PasswordPolicyProvider;
import java.io.Serializable;
@@ -33,6 +29,18 @@ import java.util.Set;
*/
public class PasswordPolicy implements Serializable {
+ public static final String HASH_ALGORITHM_ID = "hashAlgorithm";
+
+ public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2";
+
+ public static final String HASH_ITERATIONS_ID = "hashIterations";
+
+ public static final int HASH_ITERATIONS_DEFAULT = 20000;
+
+ public static final String PASSWORD_HISTORY_ID = "passwordHistory";
+
+ public static final String FORCE_EXPIRED_ID = "forceExpiredPasswordChange";
+
private String policyString;
private Map policyConfig;
@@ -84,32 +92,32 @@ public class PasswordPolicy implements Serializable {
}
public String getHashAlgorithm() {
- if (policyConfig.containsKey(HashAlgorithmPasswordPolicyProviderFactory.ID)) {
- return getPolicyConfig(HashAlgorithmPasswordPolicyProviderFactory.ID);
+ if (policyConfig.containsKey(HASH_ALGORITHM_ID)) {
+ return getPolicyConfig(HASH_ALGORITHM_ID);
} else {
- return HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE;
+ return HASH_ALGORITHM_DEFAULT;
}
}
public int getHashIterations() {
- if (policyConfig.containsKey(HashIterationsPasswordPolicyProviderFactory.ID)) {
- return getPolicyConfig(HashIterationsPasswordPolicyProviderFactory.ID);
+ if (policyConfig.containsKey(HASH_ITERATIONS_ID)) {
+ return getPolicyConfig(HASH_ITERATIONS_ID);
} else {
- return HashIterationsPasswordPolicyProviderFactory.DEFAULT_VALUE;
+ return HASH_ITERATIONS_DEFAULT;
}
}
public int getExpiredPasswords() {
- if (policyConfig.containsKey(HistoryPasswordPolicyProviderFactory.ID)) {
- return getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
+ if (policyConfig.containsKey(PASSWORD_HISTORY_ID)) {
+ return getPolicyConfig(PASSWORD_HISTORY_ID);
} else {
return -1;
}
}
public int getDaysToExpirePassword() {
- if (policyConfig.containsKey(ForceExpiredPasswordPolicyProviderFactory.ID)) {
- return getPolicyConfig(ForceExpiredPasswordPolicyProviderFactory.ID);
+ if (policyConfig.containsKey(FORCE_EXPIRED_ID)) {
+ return getPolicyConfig(FORCE_EXPIRED_ID);
} else {
return -1;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 1765bad2092..707b96ae3da 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,8 +19,6 @@ package org.keycloak.models;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.services.managers.UserManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -74,7 +72,8 @@ public class UserFederationManager implements UserProvider {
}
public UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
- return KeycloakModelUtils.getFederationProviderInstance(session, model);
+ UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
+ return factory.getInstance(session, model);
}
public UserFederationProvider getFederationLink(RealmModel realm, UserModel user) {
@@ -122,7 +121,7 @@ public class UserFederationManager implements UserProvider {
}
protected void deleteInvalidUser(final RealmModel realm, final UserModel user) {
- KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+ runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
@@ -136,6 +135,29 @@ public class UserFederationManager implements UserProvider {
});
}
+ private static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) {
+ KeycloakSession session = factory.create();
+ KeycloakTransaction tx = session.getTransactionManager();
+ try {
+ tx.begin();
+ task.run(session);
+
+ if (tx.isActive()) {
+ if (tx.getRollbackOnly()) {
+ tx.rollback();
+ } else {
+ tx.commit();
+ }
+ }
+ } catch (RuntimeException re) {
+ if (tx.isActive()) {
+ tx.rollback();
+ }
+ throw re;
+ } finally {
+ session.close();
+ }
+ }
protected UserModel validateAndProxyUser(RealmModel realm, UserModel user) {
UserModel managed = managedUsers.get(user.getId());
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/UserManager.java b/server-spi/src/main/java/org/keycloak/models/UserManager.java
similarity index 87%
rename from server-spi/src/main/java/org/keycloak/services/managers/UserManager.java
rename to server-spi/src/main/java/org/keycloak/models/UserManager.java
index d0e894782fc..81b2b51a98b 100755
--- a/server-spi/src/main/java/org/keycloak/services/managers/UserManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserManager.java
@@ -15,13 +15,8 @@
* limitations under the License.
*/
-package org.keycloak.services.managers;
+package org.keycloak.models;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserProvider;
-import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
/**
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
new file mode 100644
index 00000000000..a03a55d9f0a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
@@ -0,0 +1,101 @@
+/*
+ * 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.models.utils;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RoleModel;
+
+import java.util.Set;
+import java.util.stream.StreamSupport;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class RoleUtils {
+
+ /**
+ *
+ * @param groups
+ * @param targetGroup
+ * @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
+ */
+ public static boolean isMember(Set groups, GroupModel targetGroup) {
+ if (groups.contains(targetGroup)) return true;
+
+ for (GroupModel mapping : groups) {
+ GroupModel child = mapping;
+ while(child.getParent() != null) {
+ if (child.getParent().equals(targetGroup)) return true;
+ child = child.getParent();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param roles
+ * @param targetRole
+ * @return true if targetRole is in roles (directly or indirectly via composite role)
+ */
+ public static boolean hasRole(Set roles, RoleModel targetRole) {
+ if (roles.contains(targetRole)) return true;
+
+ for (RoleModel mapping : roles) {
+ if (mapping.hasRole(targetRole)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the {@code targetRole} is contained in the given group or its parents
+ * (if requested)
+ * @param group Group to check role for
+ * @param targetRole
+ * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
+ * @return true if targetRole is in roles (directly or indirectly via composite role)
+ */
+ public static boolean hasRoleFromGroup(GroupModel group, RoleModel targetRole, boolean checkParentGroup) {
+ if (group.hasRole(targetRole))
+ return true;
+
+ if (checkParentGroup) {
+ GroupModel parent = group.getParent();
+ return parent != null && hasRoleFromGroup(parent, targetRole, true);
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents
+ * (if requested)
+ * @param groups
+ * @param targetRole
+ * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
+ * @return true if targetRole is in roles (directly or indirectly via composite role)
+ */
+ public static boolean hasRoleFromGroup(Iterable groups, RoleModel targetRole, boolean checkParentGroup) {
+ if (groups == null) {
+ return false;
+ }
+
+ return StreamSupport.stream(groups.spliterator(), false)
+ .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/provider/Spi.java b/server-spi/src/main/java/org/keycloak/provider/Spi.java
index b9c47f8e5cb..8043bcf2e0c 100644
--- a/server-spi/src/main/java/org/keycloak/provider/Spi.java
+++ b/server-spi/src/main/java/org/keycloak/provider/Spi.java
@@ -17,9 +17,6 @@
package org.keycloak.provider;
-import java.util.Collections;
-import java.util.List;
-
/**
* @author Stian Thorgersen
*/
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
index df6c37ab2ab..c15902090e9 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
@@ -26,7 +26,7 @@ import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultRoles;
-import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
import org.keycloak.storage.StorageId;
import java.util.Collections;
@@ -135,7 +135,7 @@ public abstract class AbstractUserAdapter implements UserModel {
@Override
public boolean isMemberOf(GroupModel group) {
Set roles = getGroups();
- return KeycloakModelUtils.isMember(roles, group);
+ return RoleUtils.isMember(roles, group);
}
@Override
@@ -172,8 +172,8 @@ public abstract class AbstractUserAdapter implements UserModel {
@Override
public boolean hasRole(RoleModel role) {
Set roles = getRoleMappings();
- return KeycloakModelUtils.hasRole(roles, role)
- || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+ return RoleUtils.hasRole(roles, role)
+ || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
index 865f454e2cf..c93dac49b2b 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
@@ -25,7 +25,7 @@ import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultRoles;
-import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
@@ -140,7 +140,7 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
@Override
public boolean isMemberOf(GroupModel group) {
Set roles = getGroups();
- return KeycloakModelUtils.isMember(roles, group);
+ return RoleUtils.isMember(roles, group);
}
@Override
@@ -177,8 +177,8 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
@Override
public boolean hasRole(RoleModel role) {
Set roles = getRoleMappings();
- return KeycloakModelUtils.hasRole(roles, role)
- || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+ return RoleUtils.hasRole(roles, role)
+ || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
}
@Override
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index bbd588ea50b..c7ee4865c70 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -15,54 +15,21 @@
# limitations under the License.
#
-org.keycloak.models.UserFederationSpi
-org.keycloak.storage.UserStorageProviderSpi
-org.keycloak.storage.federated.UserFederatedStorageProviderSpi
-org.keycloak.mappers.UserFederationMapperSpi
-org.keycloak.models.RealmSpi
-org.keycloak.models.UserSessionSpi
-org.keycloak.models.UserSpi
-org.keycloak.models.session.UserSessionPersisterSpi
-org.keycloak.models.dblock.DBLockSpi
-org.keycloak.migration.MigrationSpi
-org.keycloak.events.EventListenerSpi
-org.keycloak.events.EventStoreSpi
-org.keycloak.exportimport.ExportSpi
-org.keycloak.exportimport.ImportSpi
-org.keycloak.timer.TimerSpi
-org.keycloak.scripting.ScriptingSpi
-org.keycloak.services.managers.BruteForceProtectorSpi
-org.keycloak.services.resource.RealmResourceSPI
-org.keycloak.protocol.ClientInstallationSpi
-org.keycloak.protocol.LoginProtocolSpi
-org.keycloak.protocol.ProtocolMapperSpi
-org.keycloak.broker.provider.IdentityProviderSpi
-org.keycloak.broker.provider.IdentityProviderMapperSpi
-org.keycloak.broker.social.SocialProviderSpi
-org.keycloak.forms.account.AccountSpi
-org.keycloak.forms.login.LoginFormsSpi
-org.keycloak.email.EmailSenderSpi
-org.keycloak.email.EmailTemplateSpi
-org.keycloak.theme.ThemeSpi
-org.keycloak.truststore.TruststoreSpi
-org.keycloak.connections.httpclient.HttpClientSpi
-org.keycloak.models.cache.CacheRealmProviderSpi
-org.keycloak.models.cache.CacheUserProviderSpi
-org.keycloak.authentication.AuthenticatorSpi
-org.keycloak.authentication.ClientAuthenticatorSpi
-org.keycloak.authentication.RequiredActionSpi
-org.keycloak.authentication.FormAuthenticatorSpi
-org.keycloak.authentication.FormActionSpi
-org.keycloak.cluster.ClusterSpi
-org.keycloak.authorization.policy.provider.PolicySpi
-org.keycloak.authorization.store.StoreFactorySpi
-org.keycloak.authorization.AuthorizationSpi
-org.keycloak.models.cache.authorization.CachedStoreFactorySpi
-org.keycloak.protocol.oidc.TokenIntrospectionSpi
-org.keycloak.policy.PasswordPolicySpi
-org.keycloak.policy.PasswordPolicyManagerSpi
-org.keycloak.transaction.TransactionManagerLookupSpi
-org.keycloak.credential.hash.PasswordHashSpi
-org.keycloak.credential.CredentialSpi
-org.keycloak.keys.PublicKeyStorageSpi
-org.keycloak.keys.KeySpi
\ No newline at end of file
+#
+# 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.
+#
+
+org.keycloak.storage.UserStorageProviderSpi
\ No newline at end of file
diff --git a/services/pom.xml b/services/pom.xml
index 78e3f42dcbf..f6fdb74cc7e 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -63,6 +63,11 @@
keycloak-server-spiprovided
+
+ org.keycloak
+ keycloak-server-spi-private
+ provided
+ org.keycloakkeycloak-ldap-federation
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
index fdaa48187a5..ef17476e339 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
@@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.browser;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.RoleUtils;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
@@ -30,7 +31,6 @@ import static org.keycloak.authentication.authenticators.browser.ConditionalOtpF
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SHOW_OTP;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SKIP_OTP;
import static org.keycloak.models.utils.KeycloakModelUtils.getRoleFromString;
-import static org.keycloak.models.utils.KeycloakModelUtils.hasRole;
/**
* An {@link OTPFormAuthenticator} that can conditionally require OTP authentication.
@@ -264,6 +264,6 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator {
RoleModel role = getRoleFromString(context.getRealm(), roleName);
UserModel user = context.getUser();
- return hasRole(user.getRoleMappings(), role);
+ return RoleUtils.hasRole(user.getRoleMappings(), role);
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
index 85a217ff1e5..9bff3f99982 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
@@ -142,7 +142,7 @@ public class ScriptBasedAuthenticator implements Authenticator {
RealmModel realm = context.getRealm();
- ScriptingProvider scripting = context.getSession().scripting();
+ ScriptingProvider scripting = context.getSession().getProvider(ScriptingProvider.class);
//TODO lookup script by scriptId instead of creating it every time
ScriptModel script = scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 997bc9368ee..0ef527629dc 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -73,9 +73,13 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
-import java.security.PublicKey;
+import java.security.Key;
import java.security.cert.X509Certificate;
+import java.util.LinkedList;
import java.util.List;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
/**
* @author Bill Burke
@@ -174,14 +178,20 @@ public class SAMLEndpoint {
protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
- protected PublicKey getIDPKey() {
- X509Certificate certificate = null;
- try {
- certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
- } catch (ProcessingException e) {
- throw new RuntimeException(e);
+
+ protected KeyLocator getIDPKeyLocator() {
+ List keys = new LinkedList<>();
+
+ for (String signingCertificate : config.getSigningCertificates()) {
+ try {
+ X509Certificate cert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(signingCertificate.replaceAll("\\s", ""));
+ keys.add(cert.getPublicKey());
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ }
}
- return certificate.getPublicKey();
+
+ return new HardcodedKeyLocator(keys);
}
public Response execute(String samlRequest, String samlResponse, String relayState) {
@@ -265,14 +275,18 @@ public class SAMLEndpoint {
builder.issuer(issuerURL);
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(relayState);
+ boolean postBinding = config.isPostBindingResponse();
if (config.isWantAuthnRequestsSigned()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
- binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
+ binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm())
.signDocument();
+ if (! postBinding && config.isAddExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
+ builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+ }
}
try {
- if (config.isPostBindingResponse()) {
+ if (postBinding) {
return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
} else {
return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
@@ -418,7 +432,7 @@ public class SAMLEndpoint {
protected class PostBinding extends Binding {
@Override
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
- SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
+ SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKeyLocator());
}
@Override
@@ -440,8 +454,8 @@ public class SAMLEndpoint {
protected class RedirectBinding extends Binding {
@Override
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
- PublicKey publicKey = getIDPKey();
- SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
+ KeyLocator locator = getIDPKeyLocator();
+ SamlProtocolUtils.verifyRedirectSignature(documentHolder, locator, uriInfo, key);
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 104b8f81ca9..f96f15a07f6 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -50,8 +50,11 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
/**
* @author Pedro Igor
@@ -97,18 +100,22 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider 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() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+ keys.addAll(session.keys().getKeys(realm, false));
+ for (KeyMetadata key : keys) {
+ addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+ }
+ String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}
+ private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+ if (key == null) {
+ return;
+ }
+
+ target.append(SPMetadataDescriptor.xmlKeyInfo(" ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, true));
+ }
+
public SignatureAlgorithm getSignatureAlgorithm() {
String alg = getConfig().getSignatureAlgorithm();
if (alg != null) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index 1b2fb67da42..59b46ca9ca6 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -62,14 +62,45 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
}
+ /**
+ * @deprecated Prefer {@link #getSigningCertificates()}}
+ * @param signingCertificate
+ */
public String getSigningCertificate() {
- return getConfig().get("signingCertificate");
+ return getConfig().get(SIGNING_CERTIFICATE_KEY);
}
+ /**
+ * @deprecated Prefer {@link #addSigningCertificate(String)}}
+ * @param signingCertificate
+ */
public void setSigningCertificate(String signingCertificate) {
- getConfig().put("signingCertificate", signingCertificate);
+ getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
}
+ public void addSigningCertificate(String signingCertificate) {
+ String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+ if (crt == null || crt.isEmpty()) {
+ getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
+ } else {
+ // Note that "," is not coding character per PEM format specification:
+ // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+ getConfig().put(SIGNING_CERTIFICATE_KEY, crt + "," + signingCertificate);
+ }
+ }
+
+ public String[] getSigningCertificates() {
+ String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+ if (crt == null || crt.isEmpty()) {
+ return new String[] { };
+ }
+ // Note that "," is not coding character per PEM format specification:
+ // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+ return crt.split(",");
+ }
+
+ public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
+
public String getNameIDPolicyFormat() {
return getConfig().get("nameIDPolicyFormat");
}
@@ -86,6 +117,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
}
+ public boolean isAddExtensionsElementWithKeyInfo() {
+ return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
+ }
+
+ public void setAddExtensionsElementWithKeyInfo(boolean addExtensionsElementWithKeyInfo) {
+ getConfig().put("addExtensionsElementWithKeyInfo", String.valueOf(addExtensionsElementWithKeyInfo));
+ }
+
public String getSignatureAlgorithm() {
return getConfig().get("signatureAlgorithm");
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index 714c47eebb8..0cc72da4c1e 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -108,6 +108,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
+ samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false);
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
@@ -121,7 +122,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
- samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
+ samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent());
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
} else if (keyDescriptorType.getUse() == null) {
@@ -131,8 +132,8 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
}
if (defaultCertificate != null) {
- if (samlIdentityProviderConfig.getSigningCertificate() == null) {
- samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
+ if (samlIdentityProviderConfig.getSigningCertificates().length == 0) {
+ samlIdentityProviderConfig.addSigningCertificate(defaultCertificate);
}
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index 84cd0f06a5b..bdc32e70b3c 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -140,7 +140,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, policy.getHashAlgorithm());
if (hash == null) {
logger.warnv("Realm PasswordPolicy PasswordHashProvider {0} not found", policy.getHashAlgorithm());
- return session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
+ return session.getProvider(PasswordHashProvider.class, PasswordPolicy.HASH_ALGORITHM_DEFAULT);
}
return hash;
}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index 1ea409288e8..2357a65df96 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -108,7 +108,8 @@ public class ImportUtils {
}
}
- RealmImporter realmManager = session.getContext().getRealmManager();
+ RealmManager realmManager = new RealmManager(session);
+ realmManager.setContextPath(session.getContext().getContextPath());
realmManager.importRealm(rep);
if (System.getProperty(ExportImportConfig.ACTION) != null) {
diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
index c5f09f16b2c..f6bbaeb1838 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
@@ -23,7 +23,6 @@ import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
index d7fa87544d0..8c98bb4556b 100644
--- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
@@ -24,11 +24,17 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
@@ -61,8 +67,18 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
- } catch (Exception e) {
- throw new RuntimeException("Failed to load keys", e);
+ } catch (KeyStoreException kse) {
+ throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
+ } catch (FileNotFoundException fnfe) {
+ throw new RuntimeException("File not found on server. " + fnfe.getMessage(), fnfe);
+ } catch (IOException ioe) {
+ throw new RuntimeException("IO error on server. " + ioe.getMessage(), ioe);
+ } catch (NoSuchAlgorithmException nsae) {
+ throw new RuntimeException("Algorithm not available on server. " + nsae.getMessage(), nsae);
+ } catch (CertificateException ce) {
+ throw new RuntimeException("Certificate error on server. " + ce.getMessage(), ce);
+ } catch (UnrecoverableKeyException uke) {
+ throw new RuntimeException("Keystore on server can not be recovered. " + uke.getMessage(), uke);
}
}
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
index e6ed9848dd7..51b726a57b3 100644
--- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
@@ -27,6 +27,7 @@ import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
+import org.jboss.logging.Logger;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
@@ -34,6 +35,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
* @author Stian Thorgersen
*/
public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
+ private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProviderFactory.class);
public static final String ID = "java-keystore";
@@ -77,7 +79,8 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
.loadKeys(session.getContext().getRealm(), model);
} catch (Throwable t) {
- throw new ComponentValidationException("Failed to load keys", t);
+ logger.error("Failed to load keys.", t);
+ throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
}
}
diff --git a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
index 0128e4e6683..cce0fec4da5 100755
--- a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
+++ b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
@@ -24,7 +24,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
import java.util.HashMap;
import java.util.List;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
index f00b2a766dd..60f5493c22d 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
@@ -42,8 +42,9 @@ public class RedirectUtils {
}
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
- Set validRedirects = client.getRedirectUris();
- return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, validRedirects);
+ if (client != null)
+ return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris());
+ return null;
}
public static Set resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set validRedirects) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
index 00caa116e5b..3d62a27eaed 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -101,6 +101,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
app.setFullScopeAllowed(true);
app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
+ attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, SamlProtocol.ATTRIBUTE_FALSE_VALUE); // default to false
attributes.put(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
attributes.put(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
@@ -110,7 +111,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
- if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
+ if (logoutRedirect != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
if (assertionConsumerServicePostBinding != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
index 0415a72d77b..ee5aabadf04 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -23,6 +23,8 @@ import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
/**
+ * Configuration of a SAML-enabled client.
+ *
* @author Bill Burke
* @version $Revision: 1 $
*/
@@ -116,7 +118,14 @@ public class SamlClient extends ClientConfigResolver {
public void setRequiresRealmSignature(boolean val) {
client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+ }
+ public boolean addExtensionsElementWithKeyInfo() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+ }
+
+ public void setAddExtensionsElementWithKeyInfo(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
}
public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
index e5bc2fa4e3c..0af3be07506 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
@@ -17,6 +17,7 @@
package org.keycloak.protocol.saml;
+import java.util.Objects;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.saml.SignatureAlgorithm;
@@ -89,7 +90,14 @@ public class SamlClientTemplate {
public void setRequiresRealmSignature(boolean val) {
clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+ }
+ public boolean addExtensionsElementWithKeyInfo() {
+ return Objects.equals("true", clientTemplate.getAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+ }
+
+ public void setAddExtensionsElementWithKeyInfo(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
}
public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
index 3356c314738..9837179f049 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
@@ -31,6 +31,7 @@ public interface SamlConfigAttributes {
String SAML_AUTHNSTATEMENT = "saml.authnstatement";
String SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE = "saml_force_name_id_format";
String SAML_SERVER_SIGNATURE = "saml.server.signature";
+ String SAML_SERVER_SIGNATURE_KEYINFO_EXT = "saml.server.signature.keyinfo.ext";
String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
String SAML_ENCRYPT = "saml.encrypt";
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 14726d35473..486633f46da 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -74,8 +74,10 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
/**
* @author Bill Burke
@@ -98,6 +100,7 @@ public class SamlProtocol implements LoginProtocol {
public static final String SAML_REDIRECT_BINDING = "get";
public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
+ public static final String SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO = "saml.logout.addExtensionsElementWithKeyInfo";
public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
public static final String SAML_LOGOUT_CANONICALIZATION = "SAML_LOGOUT_CANONICALIZATION";
@@ -373,7 +376,15 @@ public class SamlProtocol implements LoginProtocol {
}
Document samlDocument = null;
+ KeyManager keyManager = session.keys();
+ KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
+ boolean postBinding = isPostBinding(clientSession);
+
try {
+ if ((! postBinding) && samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+ builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+ }
+
ResponseType samlModel = builder.buildModel();
final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession);
populateRoles(roleListMapper, session, userSession, clientSession, attributeStatement);
@@ -394,22 +405,19 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
bindingBuilder.relayState(relayState);
- KeyManager keyManager = session.keys();
- KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
-
if (samlClient.requiresRealmSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
if (samlClient.requiresAssertionSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
+ bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
}
if (samlClient.requiresEncryption()) {
PublicKey publicKey = null;
@@ -496,12 +504,17 @@ public class SamlProtocol implements LoginProtocol {
if (isLogoutPostBindingForClient(clientSession)) {
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+ // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add element
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri);
} else {
logger.debug("frontchannel redirect binding");
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+ if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+ }
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri);
}
@@ -534,6 +547,7 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
binding.relayState(logoutRelayState);
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
+ boolean postBinding = isLogoutPostBindingForInitiator(userSession);
if (signingAlgorithm != null) {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
String canonicalization = userSession.getNote(SAML_LOGOUT_CANONICALIZATION);
@@ -541,7 +555,11 @@ public class SamlProtocol implements LoginProtocol {
binding.canonicalizationMethod(canonicalization);
}
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
- binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ boolean addExtension = (! postBinding) && Objects.equals("true", userSession.getNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO));
+ if (addExtension) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
+ builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+ }
}
try {
@@ -577,6 +595,7 @@ public class SamlProtocol implements LoginProtocol {
String logoutRequestString = null;
try {
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
+ // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add element
logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded();
} catch (Exception e) {
logger.warn("failed to send saml logout", e);
@@ -639,7 +658,7 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
if (samlClient.requiresRealmSignature()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
- binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
return binding;
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index e1a7c98c0de..026a54a5993 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -17,6 +17,7 @@
package org.keycloak.protocol.saml;
+import java.security.Key;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
@@ -33,6 +34,15 @@ import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Element;
/**
* @author Bill Burke
@@ -40,20 +50,36 @@ import java.security.cert.Certificate;
*/
public class SamlProtocolUtils {
-
+ /**
+ * Verifies a signature of the given SAML document using settings for the given client.
+ * Throws an exception if the client signature is expected to be present as per the client
+ * settings and it is invalid, otherwise returns back to the caller.
+ *
+ * @param client
+ * @param document
+ * @throws VerificationException
+ */
public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
SamlClient samlClient = new SamlClient(client);
if (!samlClient.requiresClientSignature()) {
return;
}
PublicKey publicKey = getSignatureValidationKey(client);
- verifyDocumentSignature(document, publicKey);
+ verifyDocumentSignature(document, new HardcodedKeyLocator(publicKey));
}
- public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
+ /**
+ * Verifies a signature of the given SAML document using keys obtained from the given key locator.
+ * Throws an exception if the client signature is invalid, otherwise returns back to the caller.
+ *
+ * @param document
+ * @param keyLocator
+ * @throws VerificationException
+ */
+ public static void verifyDocumentSignature(Document document, KeyLocator keyLocator) throws VerificationException {
SAML2Signature saml2Signature = new SAML2Signature();
try {
- if (!saml2Signature.validate(document, publicKey)) {
+ if (!saml2Signature.validate(document, keyLocator)) {
throw new VerificationException("Invalid signature on document");
}
} catch (ProcessingException e) {
@@ -61,10 +87,22 @@ public class SamlProtocolUtils {
}
}
+ /**
+ * Returns public part of SAML signing key from the client settings.
+ * @param client
+ * @return Public key for signature validation.
+ * @throws VerificationException
+ */
public static PublicKey getSignatureValidationKey(ClientModel client) throws VerificationException {
return getPublicKey(new SamlClient(client).getClientSigningCertificate());
}
+ /**
+ * Returns public part of SAML encryption key from the client settings.
+ * @param client
+ * @return Public key for encryption.
+ * @throws VerificationException
+ */
public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
}
@@ -85,7 +123,7 @@ public class SamlProtocolUtils {
return cert.getPublicKey();
}
- public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
+ public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, KeyLocator locator, UriInfo uriInformation, String paramKey) throws VerificationException {
MultivaluedMap encodedParams = uriInformation.getQueryParameters(false);
String request = encodedParams.getFirst(paramKey);
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
@@ -96,10 +134,11 @@ public class SamlProtocolUtils {
if (algorithm == null) throw new VerificationException("SigAlg was null");
if (signature == null) throw new VerificationException("Signature was null");
+ String keyId = getMessageSigningKeyId(documentHolder.getSamlObject());
+
// Shibboleth doesn't sign the document for redirect binding.
// todo maybe a flag?
-
UriBuilder builder = UriBuilder.fromPath("/")
.queryParam(paramKey, request);
if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
@@ -113,8 +152,13 @@ public class SamlProtocolUtils {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
- validator.initVerify(publicKey);
- validator.update(rawQuery.getBytes("UTF-8"));
+ Key key = locator.getKey(keyId);
+ if (key instanceof PublicKey) {
+ validator.initVerify((PublicKey) key);
+ validator.update(rawQuery.getBytes("UTF-8"));
+ } else {
+ throw new VerificationException("Invalid key locator for signature verification");
+ }
if (!validator.verify(decodedSignature)) {
throw new VerificationException("Invalid query param signature");
}
@@ -123,5 +167,32 @@ public class SamlProtocolUtils {
}
}
+ private static String getMessageSigningKeyId(SAML2Object doc) {
+ final ExtensionsType extensions;
+ if (doc instanceof RequestAbstractType) {
+ extensions = ((RequestAbstractType) doc).getExtensions();
+ } else if (doc instanceof StatusResponseType) {
+ extensions = ((StatusResponseType) doc).getExtensions();
+ } else {
+ return null;
+ }
+ if (extensions == null) {
+ return null;
+ }
+
+ for (Object ext : extensions.getAny()) {
+ if (! (ext instanceof Element)) {
+ continue;
+ }
+
+ String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
+
+ if (res != null) {
+ return res;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
index b2b4ee44406..a67374ad954 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
@@ -64,7 +64,11 @@ public class SamlRepresentationAttributes {
public String getSamlServerSignature() {
if (getAttributes() == null) return null;
return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE);
+ }
+ public String getAddExtensionsElementWithKeyInfo() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT);
}
public String getForcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index ea010856505..14c550396b1 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -74,6 +74,17 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.PublicKey;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
/**
* Resource class for the oauth/openid connect token service
@@ -336,6 +347,8 @@ public class SamlService extends AuthorizationEndpointBase {
String logoutBinding = getBindingType();
if ("true".equals(samlClient.forcePostBinding()))
logoutBinding = SamlProtocol.SAML_POST_BINDING;
+ boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
+
String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
UserSessionModel userSession = authResult.getSession();
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
@@ -347,6 +360,7 @@ public class SamlService extends AuthorizationEndpointBase {
userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO, Boolean.toString((! postBinding) && samlClient.addExtensionsElementWithKeyInfo()));
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
// remove client from logout requests
@@ -397,14 +411,17 @@ public class SamlService extends AuthorizationEndpointBase {
builder.destination(logoutBindingUri);
builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
+ boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
if (samlClient.requiresRealmSignature()) {
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
- binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
-
+ binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
+ builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+ }
}
try {
- if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
+ if (postBinding) {
return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
} else {
return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
@@ -466,7 +483,8 @@ public class SamlService extends AuthorizationEndpointBase {
return;
}
PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
- SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
+ KeyLocator clientKeyLocator = new HardcodedKeyLocator(publicKey);
+ SamlProtocolUtils.verifyRedirectSignature(documentHolder, clientKeyLocator, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
}
@Override
@@ -541,12 +559,30 @@ public class SamlService extends AuthorizationEndpointBase {
public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
String template = StreamUtil.readString(is);
- template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
- template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
- template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
- template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
- template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
- return template;
+ 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 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() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+ keys.addAll(session.keys().getKeys(realm, false));
+ for (KeyMetadata 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, KeyMetadata key, String purpose) {
+ if (key == null) {
+ return;
+ }
+
+ target.append(SPMetadataDescriptor.xmlKeyInfo(" ",
+ key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
}
@GET
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
index 2175b32cc6e..14166cef3e5 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
@@ -18,7 +18,6 @@
package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
-import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -42,14 +41,14 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
@Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
SamlClient samlClient = new SamlClient(client);
- StringBuffer buffer = new StringBuffer();
+ StringBuilder buffer = new StringBuilder();
buffer.append("\n");
baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
}
- public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
+ public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuilder buffer) {
buffer.append(" \n");
@@ -113,15 +112,6 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
buffer.append(" postBindingUrl=\"").append(bindingUrl).append("\"\n");
buffer.append(" redirectBindingUrl=\"").append(bindingUrl).append("\"");
buffer.append("/>\n");
- if (samlClient.requiresRealmSignature()) {
- buffer.append(" \n");
- buffer.append(" \n");
- buffer.append(" \n");
- buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
- buffer.append(" \n");
- buffer.append(" \n");
- buffer.append(" \n");
- }
buffer.append(" \n");
buffer.append(" \n");
}
@@ -138,7 +128,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
@Override
public String getHelpText() {
- return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory if your WAR.";
+ return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory of your WAR.";
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
index ea77d474b55..bde0ccd3d98 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
@@ -39,7 +39,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
@Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
SamlClient samlClient = new SamlClient(client);
- StringBuffer buffer = new StringBuffer();
+ StringBuilder buffer = new StringBuilder();
buffer.append("\n");
KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("\n");
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
index 4b84363809c..3c451b38570 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -32,6 +32,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.SPMetadataDescriptor;
/**
* @author Bill Burke
@@ -41,49 +46,61 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
SamlClient samlClient = new SamlClient(client);
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
- String idp = "\n" +
- "\n" +
- " \n";
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n"
+ + "\n"
+ + " \n");
if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
- idp += " " + samlClient.getNameIDFormat() + "\n";
+ sb.append(" ").append(samlClient.getNameIDFormat()).append("\n");
} else {
- idp += " urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\n" +
- " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" +
- " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n" +
- " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n";
+ sb.append(" urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\n"
+ + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n"
+ + " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n"
+ + " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n");
}
String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
- idp += "\n" +
- " \n";
- if (!samlClient.forcePostBinding()) {
- idp += " \n";
+ sb.append("\n"
+ + " \n");
+ if (! samlClient.forcePostBinding()) {
+ sb.append(" \n");
}
- idp += " \n";
- if (!samlClient.forcePostBinding()) {
- idp += " \n";
+ sb.append(" \n");
+ if (! samlClient.forcePostBinding()) {
+ sb.append(" \n");
}
- idp += " \n" +
- " \n" +
- " \n" +
- " \n" +
- " " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
- " \n" +
- " \n" +
- " \n" +
- " \n" +
- " \n" +
- "\n";
- return idp;
+
+ Set 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() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+ keys.addAll(session.keys().getKeys(realm, false));
+ for (KeyMetadata key : keys) {
+ addKeyInfo(sb, key, KeyTypes.SIGNING.value());
+ }
+
+ sb.append(" \n"
+ + "\n");
+ return sb.toString();
+ }
+
+ private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+ if (key == null) {
+ return;
+ }
+
+ target.append(SPMetadataDescriptor.xmlKeyInfo(" ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
index 9d1224259f3..63499534373 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -31,6 +31,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
/**
* @author Bill Burke
@@ -45,7 +46,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
if (logoutUrl == null) logoutUrl = client.getManagementUrl();
String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
- return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+ String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
+ return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index 99f2559ef47..07313d87ba0 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -110,13 +110,6 @@ public class DefaultKeycloakContext implements KeycloakContext {
this.connection = connection;
}
- @Override
- public RealmImporter getRealmManager() {
- RealmManager manager = new RealmManager(session);
- manager.setContextPath(getContextPath());
- return manager;
- }
-
@Override
public Locale resolveLocale(UserModel user) {
return LocaleHelper.getLocale(session, realm, user);
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 7fbd7a3d12b..4cc77a269ea 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -59,7 +59,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserProvider userModel;
private UserStorageManager userStorageManager;
private UserCredentialStoreManager userCredentialStorageManager;
- private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
private UserFederatedStorageProvider userFederatedStorageProvider;
@@ -275,14 +274,4 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
}
}
-
- @Override
- public ScriptingProvider scripting() {
-
- if (scriptingProvider == null) {
- scriptingProvider = getProvider(ScriptingProvider.class);
- }
-
- return scriptingProvider;
- }
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index d0eb66b4956..b3f2638e996 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -436,7 +436,11 @@ public class AuthenticationManager {
// refresh the cookies!
createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
- if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
+ if (userSession.isRememberMe()) {
+ createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
+ } else {
+ expireRememberMeCookie(realm, uriInfo, clientConnection);
+ }
// Update userSession note with authTime. But just if flag SSO_AUTH is not set
if (!isSSOAuthentication(clientSession)) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index b648bbd6b80..eb6fba623cd 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -27,6 +27,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index f9ae1907676..7a8964f6da0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -66,7 +66,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.validation.Validation;
diff --git a/services/src/main/resources/idp-metadata-template.xml b/services/src/main/resources/idp-metadata-template.xml
index 0a536478f68..a4416cdaa98 100755
--- a/services/src/main/resources/idp-metadata-template.xml
+++ b/services/src/main/resources/idp-metadata-template.xml
@@ -16,22 +16,12 @@
~ limitations under the License.
-->
-
-
-
-
-
-
- ${idp.signing.certificate}
-
-
-
-
+${idp.signing.certificates}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
index 28e478911c4..6f208386fdc 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
@@ -28,6 +28,7 @@
+
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
index 7392dd0950e..d6d038a3180 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
@@ -40,7 +40,13 @@ public class CustomerDatabaseServlet extends HttpServlet {
pw.println("Bill Burke");
pw.print("