diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
index dd8aff5317..02f6deacd1 100755
--- a/adapters/saml/as7-eap6/adapter/pom.xml
+++ b/adapters/saml/as7-eap6/adapter/pom.xml
@@ -78,6 +78,18 @@
7.1.2.Final
provided
+
+ org.infinispan
+ infinispan-core
+ provided
+ 5.2.20.Final
+
+
+ org.infinispan
+ infinispan-cachestore-remote
+ provided
+ 5.2.20.Final
+
org.keycloak
keycloak-saml-tomcat-adapter-core
diff --git a/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/InfinispanSessionCacheIdMapperUpdater.java b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/InfinispanSessionCacheIdMapperUpdater.java
index b6f4c23f09..dd19a7b7e6 100644
--- a/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/InfinispanSessionCacheIdMapperUpdater.java
+++ b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/InfinispanSessionCacheIdMapperUpdater.java
@@ -16,9 +16,11 @@
*/
package org.keycloak.adapters.saml.jbossweb.infinispan;
+import org.keycloak.adapters.saml.AdapterConstants;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
+import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
@@ -26,6 +28,8 @@ import org.apache.catalina.Context;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.loaders.CacheLoaderManager;
+import org.infinispan.loaders.remote.RemoteCacheStore;
import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger;
@@ -37,24 +41,12 @@ public class InfinispanSessionCacheIdMapperUpdater {
private static final Logger LOG = Logger.getLogger(InfinispanSessionCacheIdMapperUpdater.class);
- public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container/web";
-
- private static final String DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheContainerJndi";
- private static final String DEPLOYMENT_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.deploymentCacheName";
- private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
+ public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container";
public static SessionIdMapperUpdater addTokenStoreUpdaters(Context context, SessionIdMapper mapper, SessionIdMapperUpdater previousIdMapperUpdater) {
- boolean distributable = context.getDistributable();
-
- if (! distributable) {
- LOG.warnv("Deployment {0} does not use supported distributed session cache mechanism", context.getName());
- return previousIdMapperUpdater;
- }
-
ServletContext servletContext = context.getServletContext();
- String cacheContainerLookup = (servletContext != null && servletContext.getInitParameter(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME) != null)
- ? servletContext.getInitParameter(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME)
- : DEFAULT_CACHE_CONTAINER_JNDI_NAME;
+ String containerName = servletContext == null ? null : servletContext.getInitParameter(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
+ String cacheName = servletContext == null ? null : servletContext.getInitParameter(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
// the following is based on https://github.com/jbossas/jboss-as/blob/7.2.0.Final/clustering/web-infinispan/src/main/java/org/jboss/as/clustering/web/infinispan/DistributedCacheManagerFactory.java#L116-L122
String host = context.getParent() == null ? "" : context.getParent().getName();
@@ -62,43 +54,48 @@ public class InfinispanSessionCacheIdMapperUpdater {
if ("/".equals(contextPath)) {
contextPath = "/ROOT";
}
+ String deploymentSessionCacheName = host + contextPath;
- boolean deploymentSessionCacheNamePreset = servletContext != null && servletContext.getInitParameter(DEPLOYMENT_CACHE_NAME_PARAM_NAME) != null;
- String deploymentSessionCacheName = deploymentSessionCacheNamePreset
- ? servletContext.getInitParameter(DEPLOYMENT_CACHE_NAME_PARAM_NAME)
- : host + contextPath;
- boolean ssoCacheNamePreset = servletContext != null && servletContext.getInitParameter(SSO_CACHE_NAME_PARAM_NAME) != null;
- String ssoCacheName = ssoCacheNamePreset
- ? servletContext.getInitParameter(SSO_CACHE_NAME_PARAM_NAME)
- : deploymentSessionCacheName + ".ssoCache";
+ if (containerName == null || cacheName == null || deploymentSessionCacheName == null) {
+ LOG.warnv("Cannot determine parameters of SSO cache for deployment {0}.", host + contextPath);
+
+ return previousIdMapperUpdater;
+ }
+
+ String cacheContainerLookup = DEFAULT_CACHE_CONTAINER_JNDI_NAME + "/" + containerName;
try {
EmbeddedCacheManager cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
- Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(ssoCacheName);
+ Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(cacheName);
if (ssoCacheConfiguration == null) {
Configuration cacheConfiguration = cacheManager.getCacheConfiguration(deploymentSessionCacheName);
if (cacheConfiguration == null) {
- LOG.debugv("Using default cache container configuration for SSO cache. lookup={0}, looked up configuration of cache={1}", cacheContainerLookup, deploymentSessionCacheName);
+ LOG.debugv("Using default configuration for SSO cache {0}.{1}.", containerName, cacheName);
ssoCacheConfiguration = cacheManager.getDefaultCacheConfiguration();
} else {
- LOG.debugv("Using distributed HTTP session cache configuration for SSO cache. lookup={0}, configuration taken from cache={1}", cacheContainerLookup, deploymentSessionCacheName);
+ LOG.debugv("Using distributed HTTP session cache configuration for SSO cache {0}.{1}, configuration taken from cache {2}",
+ containerName, cacheName, deploymentSessionCacheName);
ssoCacheConfiguration = cacheConfiguration;
- cacheManager.defineConfiguration(ssoCacheName, ssoCacheConfiguration);
+ cacheManager.defineConfiguration(cacheName, ssoCacheConfiguration);
}
} else {
- LOG.debugv("Using custom configuration for SSO cache. lookup={0}, cache name={1}", cacheContainerLookup, ssoCacheName);
+ LOG.debugv("Using custom configuration of SSO cache {0}.{1}.", containerName, cacheName);
}
CacheMode ssoCacheMode = ssoCacheConfiguration.clustering().cacheMode();
if (ssoCacheMode != CacheMode.REPL_ASYNC && ssoCacheMode != CacheMode.REPL_SYNC) {
- LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead", ssoCacheConfiguration.clustering().cacheModeString());
+ LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead.", ssoCacheConfiguration.clustering().cacheModeString());
}
- Cache ssoCache = cacheManager.getCache(ssoCacheName, true);
- ssoCache.addListener(new SsoSessionCacheListener(mapper));
+ Cache ssoCache = cacheManager.getCache(cacheName, true);
+ final SsoSessionCacheListener listener = new SsoSessionCacheListener(ssoCache, mapper);
+ ssoCache.addListener(listener);
- LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, deploymentSessionCacheName);
+ // Not possible to add listener for cross-DC support because of too old Infinispan in AS 7
+ warnIfRemoteStoreIsUsed(ssoCache);
+
+ LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
SsoCacheSessionIdMapperUpdater updater = new SsoCacheSessionIdMapperUpdater(ssoCache, previousIdMapperUpdater);
@@ -108,4 +105,17 @@ public class InfinispanSessionCacheIdMapperUpdater {
return previousIdMapperUpdater;
}
}
+
+ private static void warnIfRemoteStoreIsUsed(Cache ssoCache) {
+ final List stores = getRemoteStores(ssoCache);
+ if (stores == null || stores.isEmpty()) {
+ return;
+ }
+
+ LOG.warnv("Unable to listen for events on remote stores configured for cache {0} (unsupported in this Infinispan limitations), logouts will not be propagated.", ssoCache.getName());
+ }
+
+ public static List getRemoteStores(Cache ssoCache) {
+ return ssoCache.getAdvancedCache().getComponentRegistry().getComponent(CacheLoaderManager.class).getCacheLoaders(RemoteCacheStore.class);
+ }
}
diff --git a/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/SsoSessionCacheListener.java b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/SsoSessionCacheListener.java
index ee100ad317..aded4a38e9 100644
--- a/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/SsoSessionCacheListener.java
+++ b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/infinispan/SsoSessionCacheListener.java
@@ -20,6 +20,7 @@ import org.keycloak.adapters.spi.SessionIdMapper;
import java.util.*;
import java.util.concurrent.*;
+import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.*;
import org.infinispan.notifications.cachelistener.event.*;
@@ -43,9 +44,12 @@ public class SsoSessionCacheListener {
private final SessionIdMapper idMapper;
+ private final Cache ssoCache;
+
private ExecutorService executor = Executors.newSingleThreadExecutor();
- public SsoSessionCacheListener(SessionIdMapper idMapper) {
+ public SsoSessionCacheListener(Cache ssoCache, SessionIdMapper idMapper) {
+ this.ssoCache = ssoCache;
this.idMapper = idMapper;
}
@@ -68,8 +72,10 @@ public class SsoSessionCacheListener {
@CacheEntryRemoved
@CacheEntryModified
public void addEvent(TransactionalEvent event) {
- if (event.isPre() == false) {
+ if (event.getGlobalTransaction() != null) {
map.get(event.getGlobalTransaction()).add(event);
+ } else {
+ processEvent(event);
}
}
@@ -87,40 +93,53 @@ public class SsoSessionCacheListener {
}
for (final Event e : events) {
- switch (e.getType()) {
- case CACHE_ENTRY_CREATED:
- this.executor.submit(new Runnable() {
- @Override public void run() {
- cacheEntryCreated((CacheEntryCreatedEvent) e);
- }
- });
- break;
+ processEvent(e);
+ }
+ }
- case CACHE_ENTRY_MODIFIED:
- this.executor.submit(new Runnable() {
- @Override public void run() {
- cacheEntryModified((CacheEntryModifiedEvent) e);
- }
- });
- break;
+ private void processEvent(final Event e) {
+ switch (e.getType()) {
+ case CACHE_ENTRY_CREATED:
+ this.executor.submit(new Runnable() {
+ @Override public void run() {
+ cacheEntryCreated((CacheEntryCreatedEvent) e);
+ }
+ });
+ break;
+
+ case CACHE_ENTRY_MODIFIED:
+ this.executor.submit(new Runnable() {
+ @Override public void run() {
+ cacheEntryModified((CacheEntryModifiedEvent) e);
+ }
+ });
+ break;
- case CACHE_ENTRY_REMOVED:
- this.executor.submit(new Runnable() {
- @Override public void run() {
- cacheEntryRemoved((CacheEntryRemovedEvent) e);
- }
- });
- break;
- }
+ case CACHE_ENTRY_REMOVED:
+ this.executor.submit(new Runnable() {
+ @Override public void run() {
+ cacheEntryRemoved((CacheEntryRemovedEvent) e);
+ }
+ });
+ break;
}
}
private void cacheEntryCreated(CacheEntryCreatedEvent event) {
- if (! (event.getKey() instanceof String) || ! (event.getValue() instanceof String[])) {
+ if (! (event.getKey() instanceof String)) {
return;
}
+
String httpSessionId = (String) event.getKey();
- String[] value = (String[]) event.getValue();
+
+ if (idMapper.hasSession(httpSessionId)) {
+ // Ignore local events generated by remote store
+ LOG.tracev("IGNORING cacheEntryCreated {0}", httpSessionId);
+ return;
+ }
+
+ String[] value = ssoCache.get((String) httpSessionId);
+
String ssoId = value[0];
String principal = value[1];
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakClusteredSsoDeploymentProcessor.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakClusteredSsoDeploymentProcessor.java
new file mode 100644
index 0000000000..0333bc9dee
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakClusteredSsoDeploymentProcessor.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2017 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.subsystem.saml.as7;
+
+import org.keycloak.adapters.saml.AdapterConstants;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.deployment.WarMetaData;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.jboss.msc.service.ServiceName;
+import org.jboss.msc.service.ServiceTarget;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KeycloakClusteredSsoDeploymentProcessor implements DeploymentUnitProcessor {
+
+ private static final Logger LOG = Logger.getLogger(KeycloakClusteredSsoDeploymentProcessor.class);
+
+ private static final String DEFAULT_CACHE_CONTAINER = "web";
+ private static final String SSO_CACHE_CONTAINER_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.containerName";
+ private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ if (isKeycloakSamlAuthMethod(deploymentUnit) && isDistributable(deploymentUnit)) {
+ addSamlReplicationConfiguration(deploymentUnit, phaseContext);
+ }
+ }
+
+ public static boolean isDistributable(final DeploymentUnit deploymentUnit) {
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return false;
+ }
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ return false;
+ }
+
+ return webMetaData.getDistributable() != null || webMetaData.getReplicationConfig() != null;
+ }
+
+ public static boolean isKeycloakSamlAuthMethod(final DeploymentUnit deploymentUnit) {
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return false;
+ }
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ return false;
+ }
+
+ if (Configuration.INSTANCE.isSecureDeployment(deploymentUnit)) {
+ return true;
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+
+ return loginConfig != null && Objects.equals(loginConfig.getAuthMethod(), "KEYCLOAK-SAML");
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+ private void addSamlReplicationConfiguration(DeploymentUnit deploymentUnit, DeploymentPhaseContext context) {
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
+
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ // Find out default names of cache container and cache
+ String cacheContainer = DEFAULT_CACHE_CONTAINER;
+ String deploymentSessionCacheName =
+ (deploymentUnit.getParent() == null
+ ? ""
+ : deploymentUnit.getParent().getName() + ".")
+ + deploymentUnit.getName();
+
+ // Update names from jboss-web.xml's
+ if (webMetaData.getReplicationConfig() != null && webMetaData.getReplicationConfig().getCacheName() != null) {
+ ServiceName sn = ServiceName.parse(webMetaData.getReplicationConfig().getCacheName());
+ cacheContainer = sn.getParent().getSimpleName();
+ deploymentSessionCacheName = sn.getSimpleName();
+ }
+ String ssoCacheName = deploymentSessionCacheName + ".ssoCache";
+
+ // Override if they were set in the context parameters
+ List contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<>();
+ }
+ for (ParamValueMetaData contextParam : contextParams) {
+ if (Objects.equals(contextParam.getParamName(), SSO_CACHE_CONTAINER_NAME_PARAM_NAME)) {
+ cacheContainer = contextParam.getParamValue();
+ } else if (Objects.equals(contextParam.getParamName(), SSO_CACHE_NAME_PARAM_NAME)) {
+ ssoCacheName = contextParam.getParamValue();
+ }
+ }
+
+ LOG.debugv("Determined SSO cache container configuration: container: {0}, cache: {1}", cacheContainer, ssoCacheName);
+// addCacheDependency(context, deploymentUnit, cacheContainer, cacheName);
+
+ // Set context parameters for SSO cache container/name
+ ParamValueMetaData paramContainer = new ParamValueMetaData();
+ paramContainer.setParamName(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
+ paramContainer.setParamValue(cacheContainer);
+ contextParams.add(paramContainer);
+
+ ParamValueMetaData paramSsoCache = new ParamValueMetaData();
+ paramSsoCache.setParamName(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
+ paramSsoCache.setParamValue(ssoCacheName);
+ contextParams.add(paramSsoCache);
+
+ webMetaData.setContextParams(contextParams);
+ }
+
+ private void addCacheDependency(DeploymentPhaseContext context, DeploymentUnit deploymentUnit, String cacheContainer, String cacheName) {
+ ServiceName jbossAsCacheContainerService = ServiceName.of("jboss", "infinispan", cacheContainer);
+ ServiceTarget st = context.getServiceTarget();
+ st.addDependency(jbossAsCacheContainerService.append(cacheName));
+ }
+
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
index d583db43d1..30a853ffa6 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
@@ -48,6 +48,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
chooseConfigDeploymentProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME,
+ Phase.POST_MODULE, // PHASE
+ Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
+ chooseClusteredSsoDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
}
@@ -60,6 +64,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
return new KeycloakAdapterConfigDeploymentProcessor();
}
+ private DeploymentUnitProcessor chooseClusteredSsoDeploymentProcessor() {
+ return new KeycloakClusteredSsoDeploymentProcessor();
+ }
+
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
index 8b94068229..3646ed4554 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
@@ -23,4 +23,6 @@ package org.keycloak.adapters.saml;
*/
public class AdapterConstants {
public static final String AUTH_DATA_PARAM_NAME="org.keycloak.saml.xml.adapterConfig";
+ public static final String REPLICATION_CONFIG_CONTAINER_PARAM_NAME = "org.keycloak.saml.replication.container";
+ public static final String REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME = "org.keycloak.saml.replication.cache.sso";
}
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
index 2bf2369ef1..7e8fb83456 100755
--- a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -152,12 +152,19 @@ public class ServletSamlSessionStore implements SamlSessionStore {
public boolean isLoggedIn() {
HttpSession session = getSession(false);
if (session == null) {
- log.debug("session was null, returning null");
+ log.debug("Session was not found");
return false;
}
+
+ if (! idMapper.hasSession(session.getId())) {
+ log.debugf("Session %s has expired on some other node", session.getId());
+ session.removeAttribute(SamlSession.class.getName());
+ return false;
+ }
+
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
if (samlSession == null) {
- log.debug("SamlSession was not in session, returning null");
+ log.debug("SamlSession was not found in the session");
return false;
}
diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml
index 3be5e7e4c0..73135d55ea 100755
--- a/adapters/saml/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml
@@ -70,6 +70,10 @@
org.infinispan
infinispan-core
+
+ org.infinispan
+ infinispan-cachestore-remote
+
org.picketbox
picketbox
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/InfinispanSessionCacheIdMapperUpdater.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/InfinispanSessionCacheIdMapperUpdater.java
index 489d1d5c67..c35db63a1f 100644
--- a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/InfinispanSessionCacheIdMapperUpdater.java
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/InfinispanSessionCacheIdMapperUpdater.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.adapters.saml.wildfly.infinispan;
+import org.keycloak.adapters.saml.AdapterConstants;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
@@ -27,6 +28,8 @@ import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.persistence.manager.PersistenceManager;
+import org.infinispan.persistence.remote.RemoteStore;
import org.jboss.logging.Logger;
/**
@@ -37,64 +40,55 @@ public class InfinispanSessionCacheIdMapperUpdater {
private static final Logger LOG = Logger.getLogger(InfinispanSessionCacheIdMapperUpdater.class);
- public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container/web";
-
- private static final String DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheContainerJndi";
- private static final String DEPLOYMENT_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.deploymentCacheName";
- private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
+ public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container";
public static SessionIdMapperUpdater addTokenStoreUpdaters(DeploymentInfo deploymentInfo, SessionIdMapper mapper, SessionIdMapperUpdater previousIdMapperUpdater) {
- boolean distributable = Objects.equals(
- deploymentInfo.getSessionManagerFactory().getClass().getName(),
- "org.wildfly.clustering.web.undertow.session.DistributableSessionManagerFactory"
- );
+ Map initParameters = deploymentInfo.getInitParameters();
+ String containerName = initParameters == null ? null : initParameters.get(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
+ String cacheName = initParameters == null ? null : initParameters.get(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
+
+ if (containerName == null || cacheName == null) {
+ LOG.warnv("Cannot determine parameters of SSO cache for deployment {0}.", deploymentInfo.getDeploymentName());
- if (! distributable) {
- LOG.warnv("Deployment {0} does not use supported distributed session cache mechanism", deploymentInfo.getDeploymentName());
return previousIdMapperUpdater;
}
- Map initParameters = deploymentInfo.getInitParameters();
- String cacheContainerLookup = (initParameters != null && initParameters.get(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME) != null)
- ? initParameters.get(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME)
- : DEFAULT_CACHE_CONTAINER_JNDI_NAME;
- boolean deploymentSessionCacheNamePreset = initParameters != null && initParameters.get(DEPLOYMENT_CACHE_NAME_PARAM_NAME) != null;
- String deploymentSessionCacheName = deploymentSessionCacheNamePreset
- ? initParameters.get(DEPLOYMENT_CACHE_NAME_PARAM_NAME)
- : deploymentInfo.getDeploymentName();
- boolean ssoCacheNamePreset = initParameters != null && initParameters.get(SSO_CACHE_NAME_PARAM_NAME) != null;
- String ssoCacheName = ssoCacheNamePreset
- ? initParameters.get(SSO_CACHE_NAME_PARAM_NAME)
- : deploymentSessionCacheName + ".ssoCache";
+ String cacheContainerLookup = DEFAULT_CACHE_CONTAINER_JNDI_NAME + "/" + containerName;
+ String deploymentSessionCacheName = deploymentInfo.getDeploymentName();
try {
EmbeddedCacheManager cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
- Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(ssoCacheName);
+ Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(cacheName);
if (ssoCacheConfiguration == null) {
Configuration cacheConfiguration = cacheManager.getCacheConfiguration(deploymentSessionCacheName);
if (cacheConfiguration == null) {
- LOG.debugv("Using default cache container configuration for SSO cache. lookup={0}, looked up configuration of cache={1}", cacheContainerLookup, deploymentSessionCacheName);
+ LOG.debugv("Using default configuration for SSO cache {0}.{1}.", containerName, cacheName);
ssoCacheConfiguration = cacheManager.getDefaultCacheConfiguration();
} else {
- LOG.debugv("Using distributed HTTP session cache configuration for SSO cache. lookup={0}, configuration taken from cache={1}", cacheContainerLookup, deploymentSessionCacheName);
+ LOG.debugv("Using distributed HTTP session cache configuration for SSO cache {0}.{1}, configuration taken from cache {2}",
+ containerName, cacheName, deploymentSessionCacheName);
ssoCacheConfiguration = cacheConfiguration;
- cacheManager.defineConfiguration(ssoCacheName, ssoCacheConfiguration);
+ cacheManager.defineConfiguration(cacheName, ssoCacheConfiguration);
}
} else {
- LOG.debugv("Using custom configuration of SSO cache. lookup={0}, cache name={1}", cacheContainerLookup, ssoCacheName);
+ LOG.debugv("Using custom configuration of SSO cache {0}.{1}.", containerName, cacheName);
}
CacheMode ssoCacheMode = ssoCacheConfiguration.clustering().cacheMode();
if (ssoCacheMode != CacheMode.REPL_ASYNC && ssoCacheMode != CacheMode.REPL_SYNC) {
- LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead", ssoCacheConfiguration.clustering().cacheModeString());
+ LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead.", ssoCacheConfiguration.clustering().cacheModeString());
}
- Cache ssoCache = cacheManager.getCache(ssoCacheName, true);
- ssoCache.addListener(new SsoSessionCacheListener(mapper));
+ Cache ssoCache = cacheManager.getCache(cacheName, true);
+ final SsoSessionCacheListener listener = new SsoSessionCacheListener(ssoCache, mapper);
+ ssoCache.addListener(listener);
- LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, deploymentSessionCacheName);
+ addSsoCacheCrossDcListener(ssoCache, listener);
+ LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
+
+ LOG.debugv("Adding session listener for SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
SsoCacheSessionIdMapperUpdater updater = new SsoCacheSessionIdMapperUpdater(ssoCache, previousIdMapperUpdater);
deploymentInfo.addSessionListener(updater);
@@ -104,4 +98,25 @@ public class InfinispanSessionCacheIdMapperUpdater {
return previousIdMapperUpdater;
}
}
+
+ private static void addSsoCacheCrossDcListener(Cache ssoCache, SsoSessionCacheListener listener) {
+ if (ssoCache.getCacheConfiguration().persistence() == null) {
+ return;
+ }
+
+ final Set stores = getRemoteStores(ssoCache);
+ if (stores == null || stores.isEmpty()) {
+ return;
+ }
+
+ LOG.infov("Listening for events on remote stores configured for cache {0}", ssoCache.getName());
+
+ for (RemoteStore store : stores) {
+ store.getRemoteCache().addClientListener(listener);
+ }
+ }
+
+ public static Set getRemoteStores(Cache ispnCache) {
+ return ispnCache.getAdvancedCache().getComponentRegistry().getComponent(PersistenceManager.class).getStores(RemoteStore.class);
+ }
}
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/SsoSessionCacheListener.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/SsoSessionCacheListener.java
index ccd102e713..6d53485aa5 100644
--- a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/SsoSessionCacheListener.java
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/infinispan/SsoSessionCacheListener.java
@@ -20,6 +20,12 @@ import org.keycloak.adapters.spi.SessionIdMapper;
import java.util.*;
import java.util.concurrent.*;
+import org.infinispan.Cache;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryRemoved;
+import org.infinispan.client.hotrod.annotation.ClientListener;
+import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
+import org.infinispan.client.hotrod.event.ClientCacheEntryRemovedEvent;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.*;
import org.infinispan.notifications.cachelistener.event.*;
@@ -34,6 +40,7 @@ import org.jboss.logging.Logger;
* @author hmlnarik
*/
@Listener
+@ClientListener
public class SsoSessionCacheListener {
private static final Logger LOG = Logger.getLogger(SsoSessionCacheListener.class);
@@ -42,14 +49,21 @@ public class SsoSessionCacheListener {
private final SessionIdMapper idMapper;
+ private final Cache ssoCache;
+
private ExecutorService executor = Executors.newSingleThreadExecutor();
- public SsoSessionCacheListener(SessionIdMapper idMapper) {
+ public SsoSessionCacheListener(Cache ssoCache, SessionIdMapper idMapper) {
+ this.ssoCache = ssoCache;
this.idMapper = idMapper;
}
@TransactionRegistered
public void startTransaction(TransactionRegisteredEvent event) {
+ if (event.getGlobalTransaction() == null) {
+ return;
+ }
+
map.put(event.getGlobalTransaction().globalId(), new ConcurrentLinkedQueue());
}
@@ -66,42 +80,56 @@ public class SsoSessionCacheListener {
@CacheEntryCreated
@CacheEntryRemoved
public void addEvent(TransactionalEvent event) {
- if (event.isPre() == false) {
+ if (event.isOriginLocal()) {
+ // Local events are processed by local HTTP session listener
+ return;
+ }
+
+ if (event.isPre()) { // only handle post events
+ return;
+ }
+
+ if (event.getGlobalTransaction() != null) {
map.get(event.getGlobalTransaction().globalId()).add(event);
+ } else {
+ processEvent(event);
}
}
@TransactionCompleted
public void endTransaction(TransactionCompletedEvent event) {
+ if (event.getGlobalTransaction() == null) {
+ return;
+ }
+
Queue events = map.remove(event.getGlobalTransaction().globalId());
if (events == null || ! event.isTransactionSuccessful()) {
return;
}
- if (event.isOriginLocal()) {
- // Local events are processed by local HTTP session listener
- return;
- }
-
for (final Event e : events) {
- switch (e.getType()) {
- case CACHE_ENTRY_CREATED:
- this.executor.submit(new Runnable() {
- @Override public void run() {
- cacheEntryCreated((CacheEntryCreatedEvent) e);
- }
- });
- break;
+ processEvent(e);
+ }
+ }
- case CACHE_ENTRY_REMOVED:
- this.executor.submit(new Runnable() {
- @Override public void run() {
- cacheEntryRemoved((CacheEntryRemovedEvent) e);
- }
- });
- break;
- }
+ private void processEvent(final Event e) {
+ switch (e.getType()) {
+ case CACHE_ENTRY_CREATED:
+ this.executor.submit(new Runnable() {
+ @Override public void run() {
+ cacheEntryCreated((CacheEntryCreatedEvent) e);
+ }
+ });
+ break;
+
+ case CACHE_ENTRY_REMOVED:
+ this.executor.submit(new Runnable() {
+ @Override public void run() {
+ cacheEntryRemoved((CacheEntryRemovedEvent) e);
+ }
+ });
+ break;
}
}
@@ -128,4 +156,40 @@ public class SsoSessionCacheListener {
this.idMapper.removeSession((String) event.getKey());
}
+
+ @ClientCacheEntryCreated
+ public void remoteCacheEntryCreated(ClientCacheEntryCreatedEvent event) {
+ if (! (event.getKey() instanceof String)) {
+ return;
+ }
+
+ String httpSessionId = (String) event.getKey();
+
+ if (idMapper.hasSession(httpSessionId)) {
+ // Ignore local events generated by remote store
+ LOG.tracev("IGNORING remoteCacheEntryCreated {0}", httpSessionId);
+ return;
+ }
+
+ String[] value = ssoCache.get((String) httpSessionId);
+
+ if (value != null) {
+ String ssoId = value[0];
+ String principal = value[1];
+
+ LOG.tracev("remoteCacheEntryCreated {0}:{1}", httpSessionId, ssoId);
+
+ this.idMapper.map(ssoId, principal, httpSessionId);
+ } else {
+ LOG.tracev("remoteCacheEntryCreated {0}", event.getKey());
+
+ }
+ }
+
+ @ClientCacheEntryRemoved
+ public void remoteCacheEntryRemoved(ClientCacheEntryRemovedEvent event) {
+ LOG.tracev("remoteCacheEntryRemoved {0}", event.getKey());
+
+ this.idMapper.removeSession((String) event.getKey());
+ }
}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakClusteredSsoDeploymentProcessor.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakClusteredSsoDeploymentProcessor.java
new file mode 100644
index 0000000000..3be66deb79
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakClusteredSsoDeploymentProcessor.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017 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.subsystem.adapter.saml.extension;
+
+import org.keycloak.adapters.saml.AdapterConstants;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.jboss.as.controller.capability.CapabilityServiceSupport;
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.common.WarMetaData;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.jboss.msc.service.ServiceController;
+import org.jboss.msc.service.ServiceName;
+import org.jboss.msc.service.ServiceTarget;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KeycloakClusteredSsoDeploymentProcessor implements DeploymentUnitProcessor {
+
+ private static final Logger LOG = Logger.getLogger(KeycloakClusteredSsoDeploymentProcessor.class);
+
+ private static final String DEFAULT_CACHE_CONTAINER = "web";
+ private static final String SSO_CACHE_CONTAINER_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.containerName";
+ private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ if (isKeycloakSamlAuthMethod(deploymentUnit) && isDistributable(deploymentUnit)) {
+ addSamlReplicationConfiguration(deploymentUnit, phaseContext);
+ }
+ }
+
+ public static boolean isDistributable(final DeploymentUnit deploymentUnit) {
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return false;
+ }
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ return false;
+ }
+
+ return webMetaData.getDistributable() != null || webMetaData.getReplicationConfig() != null;
+ }
+
+ public static boolean isKeycloakSamlAuthMethod(final DeploymentUnit deploymentUnit) {
+ if (Configuration.INSTANCE.getSecureDeployment(deploymentUnit) != null) {
+ return true;
+ }
+
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return false;
+ }
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ return false;
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+
+ return loginConfig != null && Objects.equals(loginConfig.getAuthMethod(), "KEYCLOAK-SAML");
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+ private void addSamlReplicationConfiguration(DeploymentUnit deploymentUnit, DeploymentPhaseContext context) {
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
+
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ // Find out default names of cache container and cache
+ String cacheContainer = DEFAULT_CACHE_CONTAINER;
+ String deploymentSessionCacheName =
+ (deploymentUnit.getParent() == null
+ ? ""
+ : deploymentUnit.getParent().getName() + ".")
+ + deploymentUnit.getName();
+
+ // Update names from jboss-web.xml's
+ if (webMetaData.getReplicationConfig() != null && webMetaData.getReplicationConfig().getCacheName() != null) {
+ ServiceName sn = ServiceName.parse(webMetaData.getReplicationConfig().getCacheName());
+ cacheContainer = sn.getParent().getSimpleName();
+ deploymentSessionCacheName = sn.getSimpleName();
+ }
+ String ssoCacheName = deploymentSessionCacheName + ".ssoCache";
+
+ // Override if they were set in the context parameters
+ List contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<>();
+ }
+ for (ParamValueMetaData contextParam : contextParams) {
+ if (Objects.equals(contextParam.getParamName(), SSO_CACHE_CONTAINER_NAME_PARAM_NAME)) {
+ cacheContainer = contextParam.getParamValue();
+ } else if (Objects.equals(contextParam.getParamName(), SSO_CACHE_NAME_PARAM_NAME)) {
+ ssoCacheName = contextParam.getParamValue();
+ }
+ }
+
+ LOG.debugv("Determined SSO cache container configuration: container: {0}, cache: {1}", cacheContainer, ssoCacheName);
+ addCacheDependency(context, deploymentUnit, cacheContainer, ssoCacheName);
+
+ // Set context parameters for SSO cache container/name
+ ParamValueMetaData paramContainer = new ParamValueMetaData();
+ paramContainer.setParamName(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
+ paramContainer.setParamValue(cacheContainer);
+ contextParams.add(paramContainer);
+
+ ParamValueMetaData paramSsoCache = new ParamValueMetaData();
+ paramSsoCache.setParamName(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
+ paramSsoCache.setParamValue(ssoCacheName);
+ contextParams.add(paramSsoCache);
+
+ webMetaData.setContextParams(contextParams);
+ }
+
+ private void addCacheDependency(DeploymentPhaseContext context, DeploymentUnit deploymentUnit, String cacheContainer, String cacheName) {
+ ServiceName wf10CacheContainerServiceName = ServiceName.of("jboss", "infinispan", cacheContainer);
+ final ServiceController> wf10CacheContainerService = context.getServiceRegistry().getService(wf10CacheContainerServiceName);
+
+ boolean legacy = wf10CacheContainerService != null;
+ ServiceTarget st = context.getServiceTarget();
+
+ if (legacy) {
+ ServiceName cacheServiceName = wf10CacheContainerServiceName.append(cacheName);
+ ServiceController> cacheService = context.getServiceRegistry().getService(cacheServiceName);
+ if (cacheService != null) {
+ st.addDependency(cacheServiceName);
+ }
+ } else {
+ CapabilityServiceSupport support = deploymentUnit.getAttachment(Attachments.CAPABILITY_SERVICE_SUPPORT);
+
+ ServiceName cacheServiceName = support.getCapabilityServiceName("org.wildfly.clustering.infinispan.cache." + cacheContainer + "." + cacheName);
+ ServiceController> cacheService = context.getServiceRegistry().getService(cacheServiceName);
+ if (cacheService != null) {
+ st.addDependency(cacheServiceName);
+ }
+ }
+ }
+
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java
index 79a49812d3..e9ef1a3ee1 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java
@@ -43,6 +43,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
chooseConfigDeploymentProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME,
+ Phase.POST_MODULE, // PHASE
+ Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
+ chooseClusteredSsoDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
}
@@ -54,4 +58,8 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
return new KeycloakAdapterConfigDeploymentProcessor();
}
+
+ private DeploymentUnitProcessor chooseClusteredSsoDeploymentProcessor() {
+ return new KeycloakClusteredSsoDeploymentProcessor();
+ }
}
diff --git a/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/InMemorySessionIdMapper.java b/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/InMemorySessionIdMapper.java
index a00ae829f0..7ca8af6e70 100755
--- a/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/InMemorySessionIdMapper.java
+++ b/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/InMemorySessionIdMapper.java
@@ -67,6 +67,11 @@ public class InMemorySessionIdMapper implements SessionIdMapper {
ssoToSession.put(sso, session);
sessionToSso.put(session, sso);
}
+
+ if (principal == null) {
+ return;
+ }
+
Set userSessions = principalToSession.get(principal);
if (userSessions == null) {
final Set tmp = Collections.synchronizedSet(new HashSet());
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
index fd9d2e4089..885470fb2e 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
@@ -34,7 +34,6 @@
-
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
index 857a8e31b8..b61266df6c 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
@@ -40,5 +40,6 @@
+
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl
index b6fbd2e53c..9a68ecf0e3 100644
--- a/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl
@@ -39,6 +39,8 @@
+
+
@@ -57,6 +59,8 @@
+
+
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClient.java
index a17a75a850..257afc0db0 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClient.java
@@ -51,6 +51,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -289,6 +290,18 @@ public class SamlClient {
}
}
+ public void execute(Step... steps) {
+ executeAndTransform(resp -> null, Arrays.asList(steps));
+ }
+
+ public void execute(List steps) {
+ executeAndTransform(resp -> null, steps);
+ }
+
+ public T executeAndTransform(ResultExtractor resultTransformer, Step... steps) {
+ return executeAndTransform(resultTransformer, Arrays.asList(steps));
+ }
+
public T executeAndTransform(ResultExtractor resultTransformer, List steps) {
CloseableHttpResponse currentResponse = null;
URI currentUri = URI.create("about:blank");
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClientBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClientBuilder.java
index 89d309249c..3879447fb5 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClientBuilder.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClientBuilder.java
@@ -33,6 +33,10 @@ import org.keycloak.testsuite.util.saml.IdPInitiatedLoginBuilder;
import org.keycloak.testsuite.util.saml.LoginBuilder;
import org.keycloak.testsuite.util.saml.ModifySamlResponseStepBuilder;
import org.keycloak.testsuite.util.saml.RequiredConsentBuilder;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.hamcrest.Matcher;
+import org.junit.Assert;
import org.w3c.dom.Document;
/**
@@ -43,6 +47,19 @@ public class SamlClientBuilder {
private final List steps = new LinkedList<>();
+ /**
+ * Execute the current steps without any work on the final response.
+ * @return Client that executed the steps
+ */
+ public SamlClient execute() {
+ return execute(resp -> {});
+ }
+
+ /**
+ * Execute the current steps and pass the final response to the {@code resultConsumer} for processing.
+ * @param resultConsumer This function is given the final response
+ * @return Client that executed the steps
+ */
public SamlClient execute(Consumer resultConsumer) {
final SamlClient samlClient = new SamlClient();
samlClient.executeAndTransform(r -> {
@@ -52,6 +69,11 @@ public class SamlClientBuilder {
return samlClient;
}
+ /**
+ * Execute the current steps and pass the final response to the {@code resultTransformer} for processing.
+ * @param resultTransformer This function is given the final response and processes it into some value
+ * @return Value returned by {@code resultTransformer}
+ */
public T executeAndTransform(ResultExtractor resultTransformer) {
return new SamlClient().executeAndTransform(resultTransformer, steps);
}
@@ -60,11 +82,48 @@ public class SamlClientBuilder {
return steps;
}
- public T addStep(T step) {
+ public T addStepBuilder(T step) {
steps.add(step);
return step;
}
+ /**
+ * Adds a single generic step
+ * @param step
+ * @return This builder
+ */
+ public SamlClientBuilder addStep(Step step) {
+ steps.add(step);
+ return this;
+ }
+
+ /**
+ * Adds a single generic step
+ * @param step
+ * @return This builder
+ */
+ public SamlClientBuilder addStep(Runnable stepWithNoParameters) {
+ addStep((client, currentURI, currentResponse, context) -> {
+ stepWithNoParameters.run();
+ return null;
+ });
+ return this;
+ }
+
+ public SamlClientBuilder assertResponse(Matcher matcher) {
+ steps.add((client, currentURI, currentResponse, context) -> {
+ Assert.assertThat(currentResponse, matcher);
+ return null;
+ });
+ return this;
+ }
+
+ /**
+ * When executing the {@link HttpUriRequest} obtained from the previous step,
+ * do not to follow HTTP redirects but pass the first response immediately
+ * to the following step.
+ * @return This builder
+ */
public SamlClientBuilder doNotFollowRedirects() {
this.steps.add(new DoNotFollowRedirectStep());
return this;
@@ -80,32 +139,32 @@ public class SamlClientBuilder {
/** Creates fresh and issues an AuthnRequest to the SAML endpoint */
public CreateAuthnRequestStepBuilder authnRequest(URI authServerSamlUrl, String issuer, String assertionConsumerURL, Binding requestBinding) {
- return addStep(new CreateAuthnRequestStepBuilder(authServerSamlUrl, issuer, assertionConsumerURL, requestBinding, this));
+ return addStepBuilder(new CreateAuthnRequestStepBuilder(authServerSamlUrl, issuer, assertionConsumerURL, requestBinding, this));
}
/** Issues the given AuthnRequest to the SAML endpoint */
public CreateAuthnRequestStepBuilder authnRequest(URI authServerSamlUrl, Document authnRequestDocument, Binding requestBinding) {
- return addStep(new CreateAuthnRequestStepBuilder(authServerSamlUrl, authnRequestDocument, requestBinding, this));
+ return addStepBuilder(new CreateAuthnRequestStepBuilder(authServerSamlUrl, authnRequestDocument, requestBinding, this));
}
/** Issues the given AuthnRequest to the SAML endpoint */
public CreateLogoutRequestStepBuilder logoutRequest(URI authServerSamlUrl, String issuer, Binding requestBinding) {
- return addStep(new CreateLogoutRequestStepBuilder(authServerSamlUrl, issuer, requestBinding, this));
+ return addStepBuilder(new CreateLogoutRequestStepBuilder(authServerSamlUrl, issuer, requestBinding, this));
}
/** Handles login page */
public LoginBuilder login() {
- return addStep(new LoginBuilder(this));
+ return addStepBuilder(new LoginBuilder(this));
}
/** Starts IdP-initiated flow for the given client */
public IdPInitiatedLoginBuilder idpInitiatedLogin(URI authServerSamlUrl, String clientId) {
- return addStep(new IdPInitiatedLoginBuilder(authServerSamlUrl, clientId, this));
+ return addStepBuilder(new IdPInitiatedLoginBuilder(authServerSamlUrl, clientId, this));
}
/** Handles "Requires consent" page */
public RequiredConsentBuilder consentRequired() {
- return addStep(new RequiredConsentBuilder(this));
+ return addStepBuilder(new RequiredConsentBuilder(this));
}
/** Returns SAML request or response as replied from server. Note that the redirects are disabled for this to work. */
@@ -119,20 +178,16 @@ public class SamlClientBuilder {
public ModifySamlResponseStepBuilder processSamlResponse(Binding responseBinding) {
return
doNotFollowRedirects()
- .addStep(new ModifySamlResponseStepBuilder(responseBinding, this));
+ .addStepBuilder(new ModifySamlResponseStepBuilder(responseBinding, this));
}
public SamlClientBuilder navigateTo(String httpGetUri) {
- steps.add((client, currentURI, currentResponse, context) -> {
- return new HttpGet(httpGetUri);
- });
+ steps.add((client, currentURI, currentResponse, context) -> new HttpGet(httpGetUri));
return this;
}
public SamlClientBuilder navigateTo(URI httpGetUri) {
- steps.add((client, currentURI, currentResponse, context) -> {
- return new HttpGet(httpGetUri);
- });
+ steps.add((client, currentURI, currentResponse, context) -> new HttpGet(httpGetUri));
return this;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/AbstractSAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/AbstractSAMLAdapterClusterTest.java
index ce95631d62..f71b757eac 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/AbstractSAMLAdapterClusterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/AbstractSAMLAdapterClusterTest.java
@@ -37,7 +37,6 @@ import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import java.util.function.Consumer;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.jboss.arquillian.container.test.api.*;
@@ -49,13 +48,21 @@ import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.util.Matchers;
+import org.keycloak.testsuite.util.SamlClient;
+import org.keycloak.testsuite.util.SamlClient.Binding;
+import org.keycloak.testsuite.util.SamlClientBuilder;
+import java.net.MalformedURLException;
+import java.util.function.BiConsumer;
+import org.apache.http.client.methods.HttpGet;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.WebDriverWait;
-import static org.hamcrest.Matchers.*;
-import static org.keycloak.testsuite.AbstractAuthTest.createUserRepresentation;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.getNearestSuperclassWithAnnotation;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
@@ -130,15 +137,21 @@ public abstract class AbstractSAMLAdapterClusterTest extends AbstractServletsAda
public void startServer() throws Exception {
prepareServerDirectory("standalone-" + NODE_1_NAME);
controller.start(NODE_1_SERVER_NAME);
- prepareWorkerNode(Integer.valueOf(System.getProperty("app.server.1.management.port")));
+ prepareWorkerNode(0, Integer.valueOf(System.getProperty("app.server.1.management.port")));
prepareServerDirectory("standalone-" + NODE_2_NAME);
controller.start(NODE_2_SERVER_NAME);
- prepareWorkerNode(Integer.valueOf(System.getProperty("app.server.2.management.port")));
+ prepareWorkerNode(1, Integer.valueOf(System.getProperty("app.server.2.management.port")));
deployer.deploy(EmployeeServletDistributable.DEPLOYMENT_NAME);
deployer.deploy(EmployeeServletDistributable.DEPLOYMENT_NAME + "_2");
}
- protected abstract void prepareWorkerNode(Integer managementPort) throws Exception;
+ /**
+ * Prepares a worker node
+ * @param nodeIndex Node index, counting from 0
+ * @param managementPort Port for management operations on this node
+ * @throws Exception
+ */
+ protected abstract void prepareWorkerNode(int nodeIndex, Integer managementPort) throws Exception;
@After
public void stopServer() {
@@ -155,41 +168,103 @@ public abstract class AbstractSAMLAdapterClusterTest extends AbstractServletsAda
loginActionsPage.setAuthRealm(DEMO);
}
- protected void testLogoutViaSessionIndex(URL employeeUrl, Consumer logoutFunction) {
- EmployeeServletDistributable page = PageFactory.initElements(driver, EmployeeServletDistributable.class);
- page.setUrl(employeeUrl);
- page.getUriBuilder().port(HTTP_PORT_NODE_REVPROXY);
-
- UserRepresentation bburkeUser = createUserRepresentation("bburke", "bburke@redhat.com", "Bill", "Burke", true);
+ protected void testLogoutViaSessionIndex(URL employeeUrl, boolean forceRefreshAtOtherNode, BiConsumer logoutFunction) {
setPasswordFor(bburkeUser, CredentialRepresentation.PASSWORD);
- assertSuccessfulLogin(page, bburkeUser, testRealmSAMLPostLoginPage, "principal=bburke");
+ final String employeeUrlString;
+ try {
+ URL employeeUrlAtRevProxy = new URL(employeeUrl.getProtocol(), employeeUrl.getHost(), HTTP_PORT_NODE_REVPROXY, employeeUrl.getFile());
+ employeeUrlString = employeeUrlAtRevProxy.toString();
+ } catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
- updateProxy(NODE_2_NAME, NODE_2_URI, NODE_1_URI);
- logoutFunction.accept(page);
- delayedCheckLoggedOut(page, loginActionsPage);
+ SamlClientBuilder builder = new SamlClientBuilder()
+ // Go to employee URL at reverse proxy which is set to forward to first node
+ .navigateTo(employeeUrlString)
+ // process redirection to login page
+ .processSamlResponse(Binding.POST).build()
+ .login().user(bburkeUser).build()
+ .processSamlResponse(Binding.POST).build()
+
+ // Returned to the page
+ .assertResponse(Matchers.bodyHC(containsString("principal=bburke")))
+
+ // Update the proxy to forward to the second node.
+ .addStep(() -> updateProxy(NODE_2_NAME, NODE_2_URI, NODE_1_URI));
+
+ if (forceRefreshAtOtherNode) {
+ // Go to employee URL at reverse proxy which is set to forward to _second_ node now
+ builder
+ .navigateTo(employeeUrlString)
+ .doNotFollowRedirects()
+ .assertResponse(Matchers.bodyHC(containsString("principal=bburke")));
+ }
+
+ // Logout at the _second_ node
+ logoutFunction.accept(builder, employeeUrlString);
+
+ SamlClient samlClient = builder.execute();
+ delayedCheckLoggedOut(samlClient, employeeUrlString);
+
+ // Update the proxy to forward to the first node.
updateProxy(NODE_1_NAME, NODE_1_URI, NODE_2_URI);
- delayedCheckLoggedOut(page, loginActionsPage);
+ delayedCheckLoggedOut(samlClient, employeeUrlString);
+ }
+
+ private void delayedCheckLoggedOut(SamlClient samlClient, String url) {
+ Retry.execute(() -> {
+ samlClient.execute(
+ (client, currentURI, currentResponse, context) -> new HttpGet(url),
+ (client, currentURI, currentResponse, context) -> {
+ assertThat(currentResponse, Matchers.bodyHC(not(containsString("principal=bburke"))));
+ return null;
+ }
+ );
+ }, 10, 300);
+ }
+
+ private void logoutViaAdminConsole() {
+ RealmResource demoRealm = adminClient.realm(DEMO);
+ String bburkeId = ApiUtil.findUserByUsername(demoRealm, "bburke").getId();
+ demoRealm.users().get(bburkeId).logout();
+ log.infov("Logged out via admin console");
}
@Test
- public void testBackchannelLogout(@ArquillianResource
+ public void testAdminInitiatedBackchannelLogout(@ArquillianResource
@OperateOnDeployment(value = EmployeeServletDistributable.DEPLOYMENT_NAME) URL employeeUrl) throws Exception {
- testLogoutViaSessionIndex(employeeUrl, (EmployeeServletDistributable page) -> {
- RealmResource demoRealm = adminClient.realm(DEMO);
- String bburkeId = ApiUtil.findUserByUsername(demoRealm, "bburke").getId();
- demoRealm.users().get(bburkeId).logout();
- log.infov("Logged out via admin console");
+ testLogoutViaSessionIndex(employeeUrl, false, (builder, url) -> builder.addStep(this::logoutViaAdminConsole));
+ }
+
+ @Test
+ public void testAdminInitiatedBackchannelLogoutWithAssertionOfLoggedIn(@ArquillianResource
+ @OperateOnDeployment(value = EmployeeServletDistributable.DEPLOYMENT_NAME) URL employeeUrl) throws Exception {
+ testLogoutViaSessionIndex(employeeUrl, true, (builder, url) -> builder.addStep(this::logoutViaAdminConsole));
+ }
+
+ @Test
+ public void testUserInitiatedFrontchannelLogout(@ArquillianResource
+ @OperateOnDeployment(value = EmployeeServletDistributable.DEPLOYMENT_NAME) URL employeeUrl) throws Exception {
+ testLogoutViaSessionIndex(employeeUrl, false, (builder, url) -> {
+ builder
+ .navigateTo(url + "?GLO=true")
+ .processSamlResponse(Binding.POST).build() // logout request
+ .processSamlResponse(Binding.POST).build() // logout response
+ ;
});
}
@Test
- public void testFrontchannelLogout(@ArquillianResource
+ public void testUserInitiatedFrontchannelLogoutWithAssertionOfLoggedIn(@ArquillianResource
@OperateOnDeployment(value = EmployeeServletDistributable.DEPLOYMENT_NAME) URL employeeUrl) throws Exception {
- testLogoutViaSessionIndex(employeeUrl, (EmployeeServletDistributable page) -> {
- page.logout();
- log.infov("Logged out via application");
+ testLogoutViaSessionIndex(employeeUrl, true, (builder, url) -> {
+ builder
+ .navigateTo(url + "?GLO=true")
+ .processSamlResponse(Binding.POST).build() // logout request
+ .processSamlResponse(Binding.POST).build() // logout response
+ ;
});
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
index 14bd44ef05..16798d0139 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
@@ -23,7 +23,7 @@
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
forceAuthentication="false">
-
+
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
index 114d875d92..24fe3e2800 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
@@ -13,25 +13,29 @@
-
- demo
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
- /auth
- EXTERNAL
- customer-portal-subsystem
- password
-
-
-
- demo
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
- /auth
- EXTERNAL
- product-portal-subsystem
- password
-
+
+
+
+ demo
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
+ /auth
+ EXTERNAL
+ customer-portal-subsystem
+ password
+
+
+ demo
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
+ /auth
+ EXTERNAL
+ product-portal-subsystem
+ password
+
+
+
+
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/cluster/EAP6SAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/cluster/EAP6SAMLAdapterClusterTest.java
index f0a166bea4..b52a8debf9 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/cluster/EAP6SAMLAdapterClusterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/cluster/EAP6SAMLAdapterClusterTest.java
@@ -54,8 +54,8 @@ public class EAP6SAMLAdapterClusterTest extends AbstractSAMLAdapterClusterTest {
}
@Override
- protected void prepareWorkerNode(Integer managementPort) throws IOException, CliException, NumberFormatException {
- log.infov("Preparing worker node ({0})", managementPort);
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
.standalone()
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/crossdc/EAP6SAMLAdapterCrossDCTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/crossdc/EAP6SAMLAdapterCrossDCTest.java
new file mode 100644
index 0000000000..3a726ce20b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/crossdc/EAP6SAMLAdapterCrossDCTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2017 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.testsuite.adapter.crossdc;
+
+import org.keycloak.testsuite.adapter.page.EmployeeServletDistributable;
+import org.keycloak.testsuite.arquillian.annotation.*;
+
+import java.io.*;
+
+import org.keycloak.testsuite.adapter.servlet.cluster.AbstractSAMLAdapterClusterTest;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.dmr.ModelNode;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.wildfly.extras.creaper.core.*;
+import org.wildfly.extras.creaper.core.online.*;
+import org.wildfly.extras.creaper.core.online.operations.*;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.keycloak.testsuite.adapter.AbstractServletsAdapterTest.samlServletDeployment;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@Ignore("Infinispan version 5 does not support remote cache events, hence this test is left here for development purposes only")
+@AppServerContainer("app-server-eap6")
+public class EAP6SAMLAdapterCrossDCTest extends AbstractSAMLAdapterClusterTest {
+
+ @BeforeClass
+ public static void checkCrossDcTest() {
+ Assume.assumeThat("Seems not to be running cross-DC tests", System.getProperty("cache.server"), not(is("undefined")));
+ }
+
+ protected static final int PORT_OFFSET_CACHE_1 = NumberUtils.toInt(System.getProperty("cache.server.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_1 = 11222 + PORT_OFFSET_CACHE_1;
+ protected static final int PORT_OFFSET_CACHE_2 = NumberUtils.toInt(System.getProperty("cache.server.2.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_2 = 11222 + PORT_OFFSET_CACHE_2;
+
+ private final int[] CACHE_HOTROD_PORTS = new int[] { CACHE_HOTROD_PORT_CACHE_1, CACHE_HOTROD_PORT_CACHE_2 };
+ private final int[] TCPPING_PORTS = new int[] { 7600 + PORT_OFFSET_NODE_1, 7600 + PORT_OFFSET_NODE_2 };
+
+ private static final String SESSION_CACHE_NAME = EmployeeServletDistributable.DEPLOYMENT_NAME + "-cache";
+ private static final String SSO_CACHE_NAME = SESSION_CACHE_NAME + ".ssoCache";
+
+ private static final Address SESSION_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SESSION_CACHE_NAME);
+ private static final Address SSO_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SSO_CACHE_NAME);
+
+ private static final String JBOSS_WEB_XML = "\n"
+ + "\n"
+ + " \n"
+ + " SESSION\n"
+ + " " + "web." + SESSION_CACHE_NAME + "\n"
+ + " \n"
+ + "";
+
+ @TargetsContainer(value = "app-server-eap6-" + NODE_1_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME, managed = false)
+ protected static WebArchive employee() {
+ return samlServletDeployment(EmployeeServletDistributable.DEPLOYMENT_NAME,
+ EmployeeServletDistributable.DEPLOYMENT_NAME + "/WEB-INF/web.xml",
+ SendUsernameServlet.class)
+ .addAsWebInfResource(new StringAsset(JBOSS_WEB_XML), "jboss-web.xml");
+ }
+
+ @TargetsContainer(value = "app-server-eap6-" + NODE_2_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME + "_2", managed = false)
+ protected static WebArchive employee2() {
+ return employee();
+ }
+
+ @Override
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
+
+ OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
+ .standalone()
+ .hostAndPort("localhost", managementPort)
+ .protocol(ManagementProtocol.REMOTE)
+ .build());
+ Operations op = new Operations(clientWorkerNodeClient);
+
+ Batch b = new Batch();
+ Address tcppingStack = Address
+ .subsystem("jgroups")
+ .and("stack", "tcpping");
+ b.add(tcppingStack);
+ b.add(tcppingStack.and("transport", "TRANSPORT"), Values.of("socket-binding", "jgroups-tcp").and("type", "TCP"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "TCPPING"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + TCPPING_PORTS[nodeIndex] + "]"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "num_initial_members"), Values.of("value", "1"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "timeout"), Values.of("value", "3000"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "MERGE2"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "FD_SOCK").and("socket-binding", "jgroups-tcp-fd"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "FD"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "VERIFY_SUSPECT"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "pbcast.NAKACK"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "UNICAST2"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "pbcast.STABLE"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "pbcast.GMS"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "UFC"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "MFC"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "FRAG2"));
+ b.invoke("add-protocol", tcppingStack, Values.of("type", "RSVP"));
+ Assert.assertTrue("Could not add TCPPING JGroups stack", op.batch(b).isSuccess());
+
+ op.add(Address.of("socket-binding-group", "standard-sockets").and("remote-destination-outbound-socket-binding", "cache-server"),
+ Values.of("host", "localhost")
+ .and("port", CACHE_HOTROD_PORTS[nodeIndex]));
+
+ op.add(SESSION_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.add(SESSION_CACHE_ADDR.and("remote-store", "REMOTE_STORE"),
+ Values.of("remote-servers", ModelNode.fromString("[{\"outbound-socket-binding\"=>\"cache-server\"}]"))
+ .and("cache", SESSION_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ op.add(SSO_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.add(SSO_CACHE_ADDR.and("remote-store", "REMOTE_STORE"),
+ Values.of("remote-servers", ModelNode.fromString("[{\"outbound-socket-binding\"=>\"cache-server\"}]"))
+ .and("cache", SSO_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ Assert.assertTrue(op.writeAttribute(Address.subsystem("jgroups"), "default-stack", "tcpping").isSuccess());
+ Assert.assertTrue(op.writeAttribute(Address.subsystem("web"), "instance-id", "${jboss.node.name}").isSuccess());
+ op.add(Address.extension("org.keycloak.keycloak-saml-adapter-subsystem"), Values.of("module", "org.keycloak.keycloak-saml-adapter-subsystem"));
+ op.add(Address.subsystem("keycloak-saml"));
+
+ clientWorkerNodeClient.execute("reload");
+
+ log.infov("Worker node ({0}) Prepared", managementPort);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml
index 061e94e179..2fe7f5e1ba 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml
@@ -36,13 +36,19 @@
org.wildfly.extras.creaper
creaper-core
test
- 1.5.0
+ 1.6.1
org.wildfly.core
wildfly-cli
test
- 3.0.0.Beta30
+ ${wildfly.core.version}
+
+
+ org.wildfly.core
+ wildfly-controller-client
+ test
+ ${wildfly.core.version}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/cluster/WildflySAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/cluster/WildflySAMLAdapterClusterTest.java
index eb7973c37d..5735a6aed4 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/cluster/WildflySAMLAdapterClusterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/cluster/WildflySAMLAdapterClusterTest.java
@@ -53,8 +53,8 @@ public class WildflySAMLAdapterClusterTest extends AbstractSAMLAdapterClusterTes
}
@Override
- protected void prepareWorkerNode(Integer managementPort) throws IOException, CliException, NumberFormatException {
- log.infov("Preparing worker node ({0})", managementPort);
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
.standalone()
@@ -71,8 +71,6 @@ public class WildflySAMLAdapterClusterTest extends AbstractSAMLAdapterClusterTes
b.add(tcppingStack.and("protocol", "TCPPING"));
b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + (7600 + PORT_OFFSET_NODE_1) + "],localhost[" + (7600 + PORT_OFFSET_NODE_2) + "]"));
b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
- b.add(tcppingStack.and("protocol", "TCPPING").and("property", "num_initial_members"), Values.of("value", "2"));
- b.add(tcppingStack.and("protocol", "TCPPING").and("property", "timeout"), Values.of("value", "3000"));
b.add(tcppingStack.and("protocol", "MERGE3"));
b.add(tcppingStack.and("protocol", "FD_SOCK"), Values.of("socket-binding", "jgroups-tcp-fd"));
b.add(tcppingStack.and("protocol", "FD"));
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/crossdc/WildflySAMLAdapterCrossDCTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/crossdc/WildflySAMLAdapterCrossDCTest.java
new file mode 100644
index 0000000000..9288f20a9c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/crossdc/WildflySAMLAdapterCrossDCTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2017 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.testsuite.adapter.crossdc;
+
+import org.keycloak.testsuite.adapter.page.EmployeeServletDistributable;
+import org.keycloak.testsuite.arquillian.annotation.*;
+
+import java.io.*;
+
+import org.keycloak.testsuite.adapter.servlet.cluster.AbstractSAMLAdapterClusterTest;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.wildfly.extras.creaper.core.*;
+import org.wildfly.extras.creaper.core.online.*;
+import org.wildfly.extras.creaper.core.online.operations.*;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.keycloak.testsuite.adapter.AbstractServletsAdapterTest.samlServletDeployment;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@AppServerContainer("app-server-wildfly")
+public class WildflySAMLAdapterCrossDCTest extends AbstractSAMLAdapterClusterTest {
+
+ @BeforeClass
+ public static void checkCrossDcTest() {
+ Assume.assumeThat("Seems not to be running cross-DC tests", System.getProperty("cache.server"), not(is("undefined")));
+ }
+
+ protected static final int PORT_OFFSET_CACHE_1 = NumberUtils.toInt(System.getProperty("cache.server.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_1 = 11222 + PORT_OFFSET_CACHE_1;
+ protected static final int PORT_OFFSET_CACHE_2 = NumberUtils.toInt(System.getProperty("cache.server.2.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_2 = 11222 + PORT_OFFSET_CACHE_2;
+
+ private final int[] CACHE_HOTROD_PORTS = new int[] { CACHE_HOTROD_PORT_CACHE_1, CACHE_HOTROD_PORT_CACHE_2 };
+ private final int[] TCPPING_PORTS = new int[] { 7600 + PORT_OFFSET_NODE_1, 7600 + PORT_OFFSET_NODE_2 };
+
+ private static final String SESSION_CACHE_NAME = EmployeeServletDistributable.DEPLOYMENT_NAME + "-cache";
+ private static final String SSO_CACHE_NAME = SESSION_CACHE_NAME + ".ssoCache";
+
+ private static final Address SESSION_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SESSION_CACHE_NAME);
+ private static final Address SSO_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SSO_CACHE_NAME);
+
+ private static final String JBOSS_WEB_XML = "\n"
+ + "\n"
+ + " \n"
+ + " SESSION\n"
+ + " " + "web." + SESSION_CACHE_NAME + "\n"
+ + " \n"
+ + "";
+
+ @TargetsContainer(value = "app-server-wildfly-" + NODE_1_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME, managed = false)
+ protected static WebArchive employee() {
+ return samlServletDeployment(EmployeeServletDistributable.DEPLOYMENT_NAME,
+ EmployeeServletDistributable.DEPLOYMENT_NAME + "/WEB-INF/web.xml",
+ SendUsernameServlet.class)
+ .addAsWebInfResource(new StringAsset(JBOSS_WEB_XML), "jboss-web.xml");
+ }
+
+ @TargetsContainer(value = "app-server-wildfly-" + NODE_2_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME + "_2", managed = false)
+ protected static WebArchive employee2() {
+ return employee();
+ }
+
+ @Override
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
+
+ OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
+ .standalone()
+ .hostAndPort("localhost", managementPort)
+ .build());
+ Operations op = new Operations(clientWorkerNodeClient);
+
+ Batch b = new Batch();
+ Address tcppingStack = Address
+ .subsystem("jgroups")
+ .and("stack", "tcpping");
+ b.add(tcppingStack);
+ b.add(tcppingStack.and("transport", "TCP"), Values.of("socket-binding", "jgroups-tcp"));
+ b.add(tcppingStack.and("protocol", "TCPPING"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + TCPPING_PORTS[nodeIndex] + "]"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
+ b.add(tcppingStack.and("protocol", "MERGE3"));
+ b.add(tcppingStack.and("protocol", "FD_SOCK"), Values.of("socket-binding", "jgroups-tcp-fd"));
+ b.add(tcppingStack.and("protocol", "FD"));
+ b.add(tcppingStack.and("protocol", "VERIFY_SUSPECT"));
+ b.add(tcppingStack.and("protocol", "pbcast.NAKACK2"));
+ b.add(tcppingStack.and("protocol", "UNICAST3"));
+ b.add(tcppingStack.and("protocol", "pbcast.STABLE"));
+ b.add(tcppingStack.and("protocol", "pbcast.GMS"));
+ b.add(tcppingStack.and("protocol", "MFC"));
+ b.add(tcppingStack.and("protocol", "FRAG2"));
+ b.writeAttribute(Address.subsystem("jgroups").and("channel", "ee"), "stack", "tcpping");
+ op.batch(b);
+
+
+ op.add(Address.of("socket-binding-group", "standard-sockets").and("remote-destination-outbound-socket-binding", "cache-server"),
+ Values.of("host", "localhost")
+ .and("port", CACHE_HOTROD_PORTS[nodeIndex]));
+
+ op.add(SESSION_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.writeAttribute(SESSION_CACHE_ADDR.and("component", "locking"), "isolation", "REPEATABLE_READ");
+ op.writeAttribute(SESSION_CACHE_ADDR.and("component", "transaction"), "mode", "BATCH");
+ op.add(SESSION_CACHE_ADDR.and("store", "remote"),
+ Values.ofList("remote-servers", "cache-server")
+ .and("cache", SESSION_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ op.add(SSO_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.add(SSO_CACHE_ADDR.and("store", "remote"),
+ Values.ofList("remote-servers", "cache-server")
+ .and("cache", SSO_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ op.add(Address.extension("org.keycloak.keycloak-saml-adapter-subsystem"), Values.of("module", "org.keycloak.keycloak-saml-adapter-subsystem"));
+ op.add(Address.subsystem("keycloak-saml"));
+
+ clientWorkerNodeClient.execute("reload");
+
+ log.infov("Worker node ({0}) Prepared", managementPort);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java
index 5a2644846e..d80fda5aaa 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java
@@ -53,8 +53,8 @@ public class Wildfly10SAMLAdapterClusterTest extends AbstractSAMLAdapterClusterT
}
@Override
- protected void prepareWorkerNode(Integer managementPort) throws IOException, CliException, NumberFormatException {
- log.infov("Preparing worker node ({0})", managementPort);
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
.standalone()
@@ -71,8 +71,6 @@ public class Wildfly10SAMLAdapterClusterTest extends AbstractSAMLAdapterClusterT
b.add(tcppingStack.and("protocol", "TCPPING"));
b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + (7600 + PORT_OFFSET_NODE_1) + "],localhost[" + (7600 + PORT_OFFSET_NODE_2) + "]"));
b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
- b.add(tcppingStack.and("protocol", "TCPPING").and("property", "num_initial_members"), Values.of("value", "2"));
- b.add(tcppingStack.and("protocol", "TCPPING").and("property", "timeout"), Values.of("value", "3000"));
b.add(tcppingStack.and("protocol", "MERGE3"));
b.add(tcppingStack.and("protocol", "FD_SOCK"), Values.of("socket-binding", "jgroups-tcp-fd"));
b.add(tcppingStack.and("protocol", "FD"));
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/crossdc/Wildfly10SAMLAdapterCrossDCTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/crossdc/Wildfly10SAMLAdapterCrossDCTest.java
new file mode 100644
index 0000000000..9c7d935db6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/crossdc/Wildfly10SAMLAdapterCrossDCTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2017 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.testsuite.adapter.crossdc;
+
+import org.keycloak.testsuite.adapter.page.EmployeeServletDistributable;
+import org.keycloak.testsuite.arquillian.annotation.*;
+
+import java.io.*;
+
+import org.keycloak.testsuite.adapter.servlet.cluster.AbstractSAMLAdapterClusterTest;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.wildfly.extras.creaper.core.*;
+import org.wildfly.extras.creaper.core.online.*;
+import org.wildfly.extras.creaper.core.online.operations.*;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.keycloak.testsuite.adapter.AbstractServletsAdapterTest.samlServletDeployment;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10SAMLAdapterCrossDCTest extends AbstractSAMLAdapterClusterTest {
+
+ @BeforeClass
+ public static void checkCrossDcTest() {
+ Assume.assumeThat("Seems not to be running cross-DC tests", System.getProperty("cache.server"), not(is("undefined")));
+ }
+
+ protected static final int PORT_OFFSET_CACHE_1 = NumberUtils.toInt(System.getProperty("cache.server.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_1 = 11222 + PORT_OFFSET_CACHE_1;
+ protected static final int PORT_OFFSET_CACHE_2 = NumberUtils.toInt(System.getProperty("cache.server.2.port.offset"), 0);
+ protected static final int CACHE_HOTROD_PORT_CACHE_2 = 11222 + PORT_OFFSET_CACHE_2;
+
+ private final int[] CACHE_HOTROD_PORTS = new int[] { CACHE_HOTROD_PORT_CACHE_1, CACHE_HOTROD_PORT_CACHE_2 };
+ private final int[] TCPPING_PORTS = new int[] { 7600 + PORT_OFFSET_NODE_1, 7600 + PORT_OFFSET_NODE_2 };
+
+ private static final String SESSION_CACHE_NAME = EmployeeServletDistributable.DEPLOYMENT_NAME + "-cache";
+ private static final String SSO_CACHE_NAME = SESSION_CACHE_NAME + ".ssoCache";
+
+ private static final Address SESSION_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SESSION_CACHE_NAME);
+ private static final Address SSO_CACHE_ADDR = Address.subsystem("infinispan")
+ .and("cache-container", "web")
+ .and("replicated-cache", SSO_CACHE_NAME);
+
+ private static final String JBOSS_WEB_XML = "\n"
+ + "\n"
+ + " \n"
+ + " SESSION\n"
+ + " " + "web." + SESSION_CACHE_NAME + "\n"
+ + " \n"
+ + "";
+
+ @TargetsContainer(value = "app-server-wildfly10-" + NODE_1_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME, managed = false)
+ protected static WebArchive employee() {
+ return samlServletDeployment(EmployeeServletDistributable.DEPLOYMENT_NAME,
+ EmployeeServletDistributable.DEPLOYMENT_NAME + "/WEB-INF/web.xml",
+ SendUsernameServlet.class)
+ .addAsWebInfResource(new StringAsset(JBOSS_WEB_XML), "jboss-web.xml");
+ }
+
+ @TargetsContainer(value = "app-server-wildfly10-" + NODE_2_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME + "_2", managed = false)
+ protected static WebArchive employee2() {
+ return employee();
+ }
+
+ @Override
+ protected void prepareWorkerNode(int nodeIndex, Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0} @ {1})", nodeIndex, managementPort);
+
+ OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
+ .standalone()
+ .hostAndPort("localhost", managementPort)
+ .build());
+ Operations op = new Operations(clientWorkerNodeClient);
+
+ Batch b = new Batch();
+ Address tcppingStack = Address
+ .subsystem("jgroups")
+ .and("stack", "tcpping");
+ b.add(tcppingStack);
+ b.add(tcppingStack.and("transport", "TCP"), Values.of("socket-binding", "jgroups-tcp"));
+ b.add(tcppingStack.and("protocol", "TCPPING"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + TCPPING_PORTS[nodeIndex] + "]"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
+ b.add(tcppingStack.and("protocol", "MERGE3"));
+ b.add(tcppingStack.and("protocol", "FD_SOCK"), Values.of("socket-binding", "jgroups-tcp-fd"));
+ b.add(tcppingStack.and("protocol", "FD"));
+ b.add(tcppingStack.and("protocol", "VERIFY_SUSPECT"));
+ b.add(tcppingStack.and("protocol", "pbcast.NAKACK2"));
+ b.add(tcppingStack.and("protocol", "UNICAST3"));
+ b.add(tcppingStack.and("protocol", "pbcast.STABLE"));
+ b.add(tcppingStack.and("protocol", "pbcast.GMS"));
+ b.add(tcppingStack.and("protocol", "MFC"));
+ b.add(tcppingStack.and("protocol", "FRAG2"));
+ b.writeAttribute(Address.subsystem("jgroups").and("channel", "ee"), "stack", "tcpping");
+ op.batch(b);
+
+
+ op.add(Address.of("socket-binding-group", "standard-sockets").and("remote-destination-outbound-socket-binding", "cache-server"),
+ Values.of("host", "localhost")
+ .and("port", CACHE_HOTROD_PORTS[nodeIndex]));
+
+ op.add(SESSION_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.writeAttribute(SESSION_CACHE_ADDR.and("component", "locking"), "isolation", "REPEATABLE_READ");
+ op.writeAttribute(SESSION_CACHE_ADDR.and("component", "transaction"), "mode", "BATCH");
+ op.add(SESSION_CACHE_ADDR.and("store", "remote"),
+ Values.ofList("remote-servers", "cache-server")
+ .and("cache", SESSION_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ op.add(SSO_CACHE_ADDR, Values.of("statistics-enabled", "true").and("mode", "SYNC"));
+ op.add(SSO_CACHE_ADDR.and("store", "remote"),
+ Values.ofList("remote-servers", "cache-server")
+ .and("cache", SSO_CACHE_NAME)
+ .and("passivation", false)
+ .and("purge", false)
+ .and("preload", false)
+ .and("shared", true)
+ );
+
+ op.add(Address.extension("org.keycloak.keycloak-saml-adapter-subsystem"), Values.of("module", "org.keycloak.keycloak-saml-adapter-subsystem"));
+ op.add(Address.subsystem("keycloak-saml"));
+
+ clientWorkerNodeClient.execute("reload");
+
+ log.infov("Worker node ({0}) Prepared", managementPort);
+ }
+
+}