Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d731189236
97 changed files with 3130 additions and 523 deletions
|
@ -17,8 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.common;
|
package org.keycloak.common;
|
||||||
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
<local-cache name="sessions"/>
|
<local-cache name="sessions"/>
|
||||||
<local-cache name="offlineSessions"/>
|
<local-cache name="offlineSessions"/>
|
||||||
<local-cache name="loginFailures"/>
|
<local-cache name="loginFailures"/>
|
||||||
|
<local-cache name="work"/>
|
||||||
<local-cache name="realmVersions">
|
<local-cache name="realmVersions">
|
||||||
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
||||||
</local-cache>
|
</local-cache>
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"theme": {
|
"theme": {
|
||||||
"default": "keycloak",
|
|
||||||
"staticMaxAge": 2592000,
|
"staticMaxAge": 2592000,
|
||||||
"cacheTemplates": true,
|
"cacheTemplates": true,
|
||||||
"cacheThemes": true,
|
"cacheThemes": true,
|
||||||
|
|
|
@ -7,5 +7,8 @@ embed-server --server-config=standalone-ha.xml
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:add(mode="SYNC")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions/transaction=TRANSACTION:add(mode=BATCH,locking=PESSIMISTIC)
|
||||||
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
||||||
/subsystem=keycloak-server:add(web-context=auth)
|
/subsystem=keycloak-server:add(web-context=auth)
|
|
@ -6,5 +6,8 @@ embed-server --server-config=standalone.xml
|
||||||
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
|
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
|
||||||
/subsystem=infinispan/cache-container=keycloak/local-cache=offlineSessions:add()
|
/subsystem=infinispan/cache-container=keycloak/local-cache=offlineSessions:add()
|
||||||
/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
|
/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=work:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions/transaction=TRANSACTION:add(mode=BATCH,locking=PESSIMISTIC)
|
||||||
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
||||||
/subsystem=keycloak-server:add(web-context=auth)
|
/subsystem=keycloak-server:add(web-context=auth)
|
12
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
Executable file → Normal file
12
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
Executable file → Normal file
|
@ -18,17 +18,19 @@
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author rodrigo.sasaki@icarros.com.br
|
* @author rodrigo.sasaki@icarros.com.br
|
||||||
|
@ -142,6 +144,12 @@ public interface RealmResource {
|
||||||
@Path("clients-initial-access")
|
@Path("clients-initial-access")
|
||||||
ClientInitialAccessResource clientInitialAccess();
|
ClientInitialAccessResource clientInitialAccess();
|
||||||
|
|
||||||
|
@Path("partialImport")
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response partialImport(PartialImportRepresentation rep);
|
||||||
|
|
||||||
@Path("authentication")
|
@Path("authentication")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
AuthenticationManagementResource flows();
|
AuthenticationManagementResource flows();
|
||||||
|
|
|
@ -37,9 +37,9 @@ public interface UsersResource {
|
||||||
List<UserRepresentation> search(@QueryParam("username") String username,
|
List<UserRepresentation> search(@QueryParam("username") String username,
|
||||||
@QueryParam("firstName") String firstName,
|
@QueryParam("firstName") String firstName,
|
||||||
@QueryParam("lastName") String lastName,
|
@QueryParam("lastName") String lastName,
|
||||||
@QueryParam("email") String email,
|
@QueryParam("email") String email,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResults);
|
@QueryParam("max") Integer maxResults);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-client-registration-parent</artifactId>
|
<artifactId>keycloak-integration-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.7.0.Final-SNAPSHOT</version>
|
<version>1.9.0.Final-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,61 @@ kinit hnelson@KEYCLOAK.ORG
|
||||||
|
|
||||||
and provide password `secret`
|
and provide password `secret`
|
||||||
|
|
||||||
Now when you access `http://localhost:8081/auth/realms/master/account` you should be logged in automatically as user `hnelson` .
|
Now when you access `http://localhost:8081/auth/realms/master/account` you should be logged in automatically as user `hnelson` .
|
||||||
|
|
||||||
|
|
||||||
|
Create many users or offline sessions
|
||||||
|
-------------------------------------
|
||||||
|
Run testsuite with the command like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn exec:java -Pkeycloak-server -DstartTestsuiteCLI
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively if you want to use your MySQL database use the command like this (replace properties values according your DB connection):
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn exec:java -Pkeycloak-server -Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -DstartTestsuiteCLI
|
||||||
|
```
|
||||||
|
|
||||||
|
Then once CLI is started, you can use command `help` to see all the available commands.
|
||||||
|
|
||||||
|
### Creating many users
|
||||||
|
|
||||||
|
For create many users you can use command `createUsers`
|
||||||
|
For example this will create 500 users `test0, test1, test2, ... , test499` in realm `demo` and each 100 users in separate transaction. All users will be granted realm roles `user` and `admin` :
|
||||||
|
|
||||||
|
```
|
||||||
|
createUsers test test demo 0 500 100 user,admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Check count of users:
|
||||||
|
|
||||||
|
```
|
||||||
|
getUsersCount demo
|
||||||
|
```
|
||||||
|
|
||||||
|
Check if concrete user was really created:
|
||||||
|
|
||||||
|
```
|
||||||
|
getUser demo test499
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating many offline sessions
|
||||||
|
|
||||||
|
For create many offline sessions you can use command `persistSessions` . For example create 50000 sessions (each 500 in separate transaction) with command:
|
||||||
|
|
||||||
|
```
|
||||||
|
persistSessions 50000 500
|
||||||
|
```
|
||||||
|
|
||||||
|
Once users or sessions are created, you can restart to ensure the startup import of offline sessions will be triggered and you can see impact on startup time. After restart you can use command:
|
||||||
|
|
||||||
|
```
|
||||||
|
size
|
||||||
|
```
|
||||||
|
|
||||||
|
to doublecheck total count of sessions in infinispan (it will be 2 times as there is also 1 client session per each user session created)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster.infinispan;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.context.Flag;
|
||||||
|
import org.infinispan.lifecycle.ComponentStatus;
|
||||||
|
import org.infinispan.remoting.transport.Transport;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.cluster.ClusterEvent;
|
||||||
|
import org.keycloak.cluster.ClusterListener;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.cluster.ExecutionResult;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanClusterProvider implements ClusterProvider {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(InfinispanClusterProvider.class);
|
||||||
|
|
||||||
|
public static final String CLUSTER_STARTUP_TIME_KEY = "cluster-start-time";
|
||||||
|
private static final String TASK_KEY_PREFIX = "task::";
|
||||||
|
|
||||||
|
private final InfinispanClusterProviderFactory factory;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final Cache<String, Serializable> cache;
|
||||||
|
|
||||||
|
public InfinispanClusterProvider(InfinispanClusterProviderFactory factory, KeycloakSession session, Cache<String, Serializable> cache) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.session = session;
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getClusterStartupTime() {
|
||||||
|
Integer existingClusterStartTime = (Integer) cache.get(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY);
|
||||||
|
if (existingClusterStartTime != null) {
|
||||||
|
return existingClusterStartTime;
|
||||||
|
} else {
|
||||||
|
// clusterStartTime not yet initialized. Let's try to put our startupTime
|
||||||
|
int serverStartTime = (int) (session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
||||||
|
|
||||||
|
existingClusterStartTime = (Integer) cache.putIfAbsent(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY, serverStartTime);
|
||||||
|
if (existingClusterStartTime == null) {
|
||||||
|
logger.debugf("Initialized cluster startup time to %s", Time.toDate(serverStartTime).toString());
|
||||||
|
return serverStartTime;
|
||||||
|
} else {
|
||||||
|
return existingClusterStartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ExecutionResult<T> executeIfNotExecuted(String taskKey, int taskTimeoutInSeconds, Callable<T> task) {
|
||||||
|
String cacheKey = TASK_KEY_PREFIX + taskKey;
|
||||||
|
boolean locked = tryLock(cacheKey, taskTimeoutInSeconds);
|
||||||
|
if (locked) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
T result = task.call();
|
||||||
|
return ExecutionResult.executed(result);
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw re;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unexpected exception when executed task " + taskKey, e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
removeFromCache(cacheKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ExecutionResult.notExecuted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerListener(String taskKey, ClusterListener task) {
|
||||||
|
factory.registerListener(taskKey, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notify(String taskKey, ClusterEvent event) {
|
||||||
|
// Put the value to the cache to notify listeners on all the nodes
|
||||||
|
cache.put(taskKey, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getCurrentNode(Cache<String, Serializable> cache) {
|
||||||
|
Transport transport = cache.getCacheManager().getTransport();
|
||||||
|
return transport==null ? "local" : transport.getAddress().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private LockEntry createLockEntry(Cache<String, Serializable> cache) {
|
||||||
|
LockEntry lock = new LockEntry();
|
||||||
|
lock.setNode(getCurrentNode(cache));
|
||||||
|
lock.setTimestamp(Time.currentTime());
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean tryLock(String cacheKey, int taskTimeoutInSeconds) {
|
||||||
|
LockEntry myLock = createLockEntry(cache);
|
||||||
|
|
||||||
|
LockEntry existingLock = (LockEntry) cache.putIfAbsent(cacheKey, myLock);
|
||||||
|
if (existingLock != null) {
|
||||||
|
// Task likely already in progress. Check if timestamp is not outdated
|
||||||
|
int thatTime = existingLock.getTimestamp();
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
if (thatTime + taskTimeoutInSeconds < currentTime) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Task %s outdated when in progress by node %s. Will try to replace task with our node %s", cacheKey, existingLock.getNode(), myLock.getNode());
|
||||||
|
}
|
||||||
|
boolean replaced = cache.replace(cacheKey, existingLock, myLock);
|
||||||
|
if (!replaced) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Failed to replace the task %s. Other thread replaced in the meantime. Ignoring task.", cacheKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return replaced;
|
||||||
|
} else {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Task %s in progress already by node %s. Ignoring task.", cacheKey, existingLock.getNode());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Successfully acquired lock for task %s. Our node is %s", cacheKey, myLock.getNode());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void removeFromCache(String cacheKey) {
|
||||||
|
// 3 attempts to send the message (it may fail if some node fails in the meantime)
|
||||||
|
int retry = 3;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
cache.getAdvancedCache()
|
||||||
|
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
||||||
|
.remove(cacheKey);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Task %s removed from the cache", cacheKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
ComponentStatus status = cache.getStatus();
|
||||||
|
if (status.isStopping() || status.isTerminated()) {
|
||||||
|
logger.warnf("Failed to remove task %s from the cache. Cache is already terminating", cacheKey);
|
||||||
|
logger.debug(e.getMessage(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retry--;
|
||||||
|
if (retry == 0) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster.infinispan;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
import org.infinispan.notifications.Listener;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
|
||||||
|
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
|
||||||
|
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
|
||||||
|
import org.infinispan.remoting.transport.Address;
|
||||||
|
import org.infinispan.remoting.transport.Transport;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.cluster.ClusterEvent;
|
||||||
|
import org.keycloak.cluster.ClusterListener;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.cluster.ClusterProviderFactory;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanClusterProviderFactory implements ClusterProviderFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "infinispan";
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(InfinispanClusterProviderFactory.class);
|
||||||
|
|
||||||
|
private volatile Cache<String, Serializable> workCache;
|
||||||
|
|
||||||
|
private Map<String, ClusterListener> listeners = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClusterProvider create(KeycloakSession session) {
|
||||||
|
lazyInit(session);
|
||||||
|
return new InfinispanClusterProvider(this, session, workCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lazyInit(KeycloakSession session) {
|
||||||
|
if (workCache == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (workCache == null) {
|
||||||
|
workCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||||
|
workCache.getCacheManager().addListener(new ViewChangeListener());
|
||||||
|
workCache.addListener(new CacheEntryListener());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Listener
|
||||||
|
public class ViewChangeListener {
|
||||||
|
|
||||||
|
@ViewChanged
|
||||||
|
public void viewChanged(ViewChangedEvent event) {
|
||||||
|
EmbeddedCacheManager cacheManager = event.getCacheManager();
|
||||||
|
Transport transport = cacheManager.getTransport();
|
||||||
|
|
||||||
|
// Coordinator makes sure that entries for outdated nodes are cleaned up
|
||||||
|
if (transport != null && transport.isCoordinator()) {
|
||||||
|
|
||||||
|
Set<String> newAddresses = convertAddresses(event.getNewMembers());
|
||||||
|
Set<String> removedNodesAddresses = convertAddresses(event.getOldMembers());
|
||||||
|
removedNodesAddresses.removeAll(newAddresses);
|
||||||
|
|
||||||
|
if (removedNodesAddresses.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf("Nodes %s removed from cluster. Removing tasks locked by this nodes", removedNodesAddresses.toString());
|
||||||
|
|
||||||
|
Cache<String, Serializable> cache = cacheManager.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||||
|
|
||||||
|
Iterator<String> toRemove = cache.entrySet().stream().filter(new Predicate<Map.Entry<String, Serializable>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean test(Map.Entry<String, Serializable> entry) {
|
||||||
|
if (!(entry.getValue() instanceof LockEntry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LockEntry lock = (LockEntry) entry.getValue();
|
||||||
|
return removedNodesAddresses.contains(lock.getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
}).map(new Function<Map.Entry<String, Serializable>, String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apply(Map.Entry<String, Serializable> entry) {
|
||||||
|
return entry.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).iterator();
|
||||||
|
|
||||||
|
while (toRemove.hasNext()) {
|
||||||
|
String rem = toRemove.next();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Removing task %s due it's node left cluster", rem);
|
||||||
|
}
|
||||||
|
cache.remove(rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> convertAddresses(Collection<Address> addresses) {
|
||||||
|
return addresses.stream().map(new Function<Address, String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apply(Address address) {
|
||||||
|
return address.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<T> void registerListener(String taskKey, ClusterListener task) {
|
||||||
|
listeners.put(taskKey, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener
|
||||||
|
public class CacheEntryListener {
|
||||||
|
|
||||||
|
@CacheEntryCreated
|
||||||
|
public void cacheEntryCreated(CacheEntryCreatedEvent<String, Object> event) {
|
||||||
|
if (!event.isPre()) {
|
||||||
|
trigger(event.getKey(), event.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheEntryModified
|
||||||
|
public void cacheEntryModified(CacheEntryModifiedEvent<String, Object> event) {
|
||||||
|
if (!event.isPre()) {
|
||||||
|
trigger(event.getKey(), event.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trigger(String key, Object value) {
|
||||||
|
ClusterListener task = listeners.get(key);
|
||||||
|
if (task != null) {
|
||||||
|
ClusterEvent event = (ClusterEvent) value;
|
||||||
|
task.run(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster.infinispan;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class LockEntry implements Serializable {
|
||||||
|
|
||||||
|
private String node;
|
||||||
|
private int timestamp;
|
||||||
|
|
||||||
|
public String getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNode(String node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(int timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||||
import org.infinispan.manager.DefaultCacheManager;
|
import org.infinispan.manager.DefaultCacheManager;
|
||||||
import org.infinispan.manager.EmbeddedCacheManager;
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
import org.infinispan.transaction.LockingMode;
|
||||||
|
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -145,6 +147,13 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
|
||||||
|
ConfigurationBuilder replicationConfigBuilder = new ConfigurationBuilder();
|
||||||
|
if (clustered) {
|
||||||
|
replicationConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||||
|
}
|
||||||
|
Configuration replicationCacheConfiguration = replicationConfigBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationCacheConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
||||||
static final String SESSION_CACHE_NAME = "sessions";
|
static final String SESSION_CACHE_NAME = "sessions";
|
||||||
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
||||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||||
|
static final String WORK_CACHE_NAME = "work";
|
||||||
|
|
||||||
<K, V> Cache<K, V> getCache(String name);
|
<K, V> Cache<K, V> getCache(String name);
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,41 @@ import org.infinispan.Cache;
|
||||||
import org.infinispan.CacheStream;
|
import org.infinispan.CacheStream;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.ClientInitialAccessModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.UsernameLoginFailureModel;
|
||||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.*;
|
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.TimeAwareInitializerState;
|
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.stream.*;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.Comparators;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.RealmInfoUtil;
|
import org.keycloak.models.utils.RealmInfoUtil;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -411,19 +437,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getClusterStartupTime() {
|
|
||||||
TimeAwareInitializerState state = (TimeAwareInitializerState) offlineSessionCache.get(InfinispanUserSessionProviderFactory.SESSION_INITIALIZER_STATE_KEY);
|
|
||||||
int startTime;
|
|
||||||
if (state == null) {
|
|
||||||
log.warn("Cluster startup time not yet available. Fallback to local startup time");
|
|
||||||
startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
|
||||||
} else {
|
|
||||||
startTime = state.getClusterStartupTime();
|
|
||||||
}
|
|
||||||
return startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan;
|
package org.keycloak.models.sessions.infinispan;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
@ -34,9 +36,6 @@ import org.keycloak.provider.ProviderEventListener;
|
||||||
|
|
||||||
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
|
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
|
||||||
|
|
||||||
private static final String STATE_KEY_PREFIX = "initializerState";
|
|
||||||
public static final String SESSION_INITIALIZER_STATE_KEY = STATE_KEY_PREFIX + "::offlineUserSessions";
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||||
|
|
||||||
private Config.Scope config;
|
private Config.Scope config;
|
||||||
|
@ -85,9 +84,9 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
@Override
|
@Override
|
||||||
public void run(KeycloakSession session) {
|
public void run(KeycloakSession session) {
|
||||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
Cache<String, Serializable> cache = connections.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||||
|
|
||||||
InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, SESSION_INITIALIZER_STATE_KEY);
|
InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, "offlineUserSessions");
|
||||||
initializer.initCache();
|
initializer.initCache();
|
||||||
initializer.loadPersistentSessions();
|
initializer.loadPersistentSessions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,57 +20,55 @@ package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
import org.infinispan.context.Flag;
|
import org.infinispan.context.Flag;
|
||||||
import org.infinispan.distexec.DefaultExecutorService;
|
import org.infinispan.distexec.DefaultExecutorService;
|
||||||
import org.infinispan.notifications.Listener;
|
import org.infinispan.lifecycle.ComponentStatus;
|
||||||
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
|
|
||||||
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
|
|
||||||
import org.infinispan.remoting.transport.Transport;
|
import org.infinispan.remoting.transport.Transport;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakSessionTask;
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startup initialization for reading persistent userSessions/clientSessions to be filled into infinispan/memory . In cluster,
|
* Startup initialization for reading persistent userSessions/clientSessions to be filled into infinispan/memory . In cluster,
|
||||||
* the initialization is distributed among all cluster nodes, so the startup time is even faster
|
* the initialization is distributed among all cluster nodes, so the startup time is even faster
|
||||||
*
|
*
|
||||||
|
* TODO: Move to clusterService. Implementation is already pretty generic and doesn't contain any "userSession" specific stuff. All sessions-specific logic is in the SessionLoader implementation
|
||||||
|
*
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class InfinispanUserSessionInitializer {
|
public class InfinispanUserSessionInitializer {
|
||||||
|
|
||||||
|
private static final String STATE_KEY_PREFIX = "distributed::";
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
|
private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
private final Cache<String, SessionEntity> cache;
|
private final Cache<String, Serializable> workCache;
|
||||||
private final SessionLoader sessionLoader;
|
private final SessionLoader sessionLoader;
|
||||||
private final int maxErrors;
|
private final int maxErrors;
|
||||||
private final int sessionsPerSegment;
|
private final int sessionsPerSegment;
|
||||||
private final String stateKey;
|
private final String stateKey;
|
||||||
|
|
||||||
|
|
||||||
public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKey) {
|
public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, Serializable> workCache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKeySuffix) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
this.cache = cache;
|
this.workCache = workCache;
|
||||||
this.sessionLoader = sessionLoader;
|
this.sessionLoader = sessionLoader;
|
||||||
this.maxErrors = maxErrors;
|
this.maxErrors = maxErrors;
|
||||||
this.sessionsPerSegment = sessionsPerSegment;
|
this.sessionsPerSegment = sessionsPerSegment;
|
||||||
this.stateKey = stateKey;
|
this.stateKey = STATE_KEY_PREFIX + stateKeySuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initCache() {
|
public void initCache() {
|
||||||
this.cache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
|
this.workCache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,16 +92,14 @@ public class InfinispanUserSessionInitializer {
|
||||||
|
|
||||||
|
|
||||||
private boolean isFinished() {
|
private boolean isFinished() {
|
||||||
InitializerState state = (InitializerState) cache.get(stateKey);
|
InitializerState state = (InitializerState) workCache.get(stateKey);
|
||||||
return state != null && state.isFinished();
|
return state != null && state.isFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private InitializerState getOrCreateInitializerState() {
|
private InitializerState getOrCreateInitializerState() {
|
||||||
TimeAwareInitializerState state = (TimeAwareInitializerState) cache.get(stateKey);
|
InitializerState state = (InitializerState) workCache.get(stateKey);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
int startTime = (int)(sessionFactory.getServerStartupTimestamp() / 1000);
|
|
||||||
|
|
||||||
final int[] count = new int[1];
|
final int[] count = new int[1];
|
||||||
|
|
||||||
// Rather use separate transactions for update and counting
|
// Rather use separate transactions for update and counting
|
||||||
|
@ -111,7 +107,7 @@ public class InfinispanUserSessionInitializer {
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run(KeycloakSession session) {
|
public void run(KeycloakSession session) {
|
||||||
sessionLoader.init(session, startTime);
|
sessionLoader.init(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -124,9 +120,8 @@ public class InfinispanUserSessionInitializer {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
state = new TimeAwareInitializerState();
|
state = new InitializerState();
|
||||||
state.init(count[0], sessionsPerSegment);
|
state.init(count[0], sessionsPerSegment);
|
||||||
state.setClusterStartupTime(startTime);
|
|
||||||
saveStateToCache(state);
|
saveStateToCache(state);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
@ -143,7 +138,7 @@ public class InfinispanUserSessionInitializer {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
// Save this synchronously to ensure all nodes read correct state
|
// Save this synchronously to ensure all nodes read correct state
|
||||||
InfinispanUserSessionInitializer.this.cache.getAdvancedCache().
|
InfinispanUserSessionInitializer.this.workCache.getAdvancedCache().
|
||||||
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
||||||
.put(stateKey, state);
|
.put(stateKey, state);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +148,7 @@ public class InfinispanUserSessionInitializer {
|
||||||
|
|
||||||
|
|
||||||
private boolean isCoordinator() {
|
private boolean isCoordinator() {
|
||||||
Transport transport = cache.getCacheManager().getTransport();
|
Transport transport = workCache.getCacheManager().getTransport();
|
||||||
return transport == null || transport.isCoordinator();
|
return transport == null || transport.isCoordinator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,9 +161,9 @@ public class InfinispanUserSessionInitializer {
|
||||||
int processors = Runtime.getRuntime().availableProcessors();
|
int processors = Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
ExecutorService localExecutor = Executors.newCachedThreadPool();
|
ExecutorService localExecutor = Executors.newCachedThreadPool();
|
||||||
Transport transport = cache.getCacheManager().getTransport();
|
Transport transport = workCache.getCacheManager().getTransport();
|
||||||
boolean distributed = transport != null;
|
boolean distributed = transport != null;
|
||||||
ExecutorService executorService = distributed ? new DefaultExecutorService(cache, localExecutor) : localExecutor;
|
ExecutorService executorService = distributed ? new DefaultExecutorService(workCache, localExecutor) : localExecutor;
|
||||||
|
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
|
|
||||||
|
@ -190,7 +185,7 @@ public class InfinispanUserSessionInitializer {
|
||||||
SessionInitializerWorker worker = new SessionInitializerWorker();
|
SessionInitializerWorker worker = new SessionInitializerWorker();
|
||||||
worker.setWorkerEnvironment(segment, sessionsPerSegment, sessionLoader);
|
worker.setWorkerEnvironment(segment, sessionsPerSegment, sessionLoader);
|
||||||
if (!distributed) {
|
if (!distributed) {
|
||||||
worker.setEnvironment(cache, null);
|
worker.setEnvironment(workCache, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<WorkerResult> future = executorService.submit(worker);
|
Future<WorkerResult> future = executorService.submit(worker);
|
||||||
|
@ -242,6 +237,12 @@ public class InfinispanUserSessionInitializer {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
return;
|
return;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
ComponentStatus status = workCache.getStatus();
|
||||||
|
if (status.isStopping() || status.isTerminated()) {
|
||||||
|
log.warn("Failed to put initializerState to the cache. Cache is already terminating");
|
||||||
|
log.debug(e.getMessage(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
retry--;
|
retry--;
|
||||||
if (retry == 0) {
|
if (retry == 0) {
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -33,9 +34,12 @@ public class OfflineUserSessionLoader implements SessionLoader {
|
||||||
private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
|
private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(KeycloakSession session, int clusterStartupTime) {
|
public void init(KeycloakSession session) {
|
||||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// TODO: check if update of timestamps in persister can be skipped entirely
|
||||||
|
int clusterStartupTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
|
||||||
|
|
||||||
log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", clusterStartupTime);
|
log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", clusterStartupTime);
|
||||||
|
|
||||||
persister.clearDetachedUserSessions();
|
persister.clearDetachedUserSessions();
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class SessionInitializerWorker implements DistributedCallable<String, SessionEntity, InfinispanUserSessionInitializer.WorkerResult>, Serializable {
|
public class SessionInitializerWorker implements DistributedCallable<String, Serializable, InfinispanUserSessionInitializer.WorkerResult>, Serializable {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(SessionInitializerWorker.class);
|
private static final Logger log = Logger.getLogger(SessionInitializerWorker.class);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ public class SessionInitializerWorker implements DistributedCallable<String, Ses
|
||||||
private int sessionsPerSegment;
|
private int sessionsPerSegment;
|
||||||
private SessionLoader sessionLoader;
|
private SessionLoader sessionLoader;
|
||||||
|
|
||||||
private transient Cache<String, SessionEntity> cache;
|
private transient Cache<String, Serializable> workCache;
|
||||||
|
|
||||||
public void setWorkerEnvironment(int segment, int sessionsPerSegment, SessionLoader sessionLoader) {
|
public void setWorkerEnvironment(int segment, int sessionsPerSegment, SessionLoader sessionLoader) {
|
||||||
this.segment = segment;
|
this.segment = segment;
|
||||||
|
@ -49,8 +49,8 @@ public class SessionInitializerWorker implements DistributedCallable<String, Ses
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnvironment(Cache<String, SessionEntity> cache, Set<String> inputKeys) {
|
public void setEnvironment(Cache<String, Serializable> workCache, Set<String> inputKeys) {
|
||||||
this.cache = cache;
|
this.workCache = workCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,7 +59,7 @@ public class SessionInitializerWorker implements DistributedCallable<String, Ses
|
||||||
log.tracef("Running computation for segment: %d", segment);
|
log.tracef("Running computation for segment: %d", segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeycloakSessionFactory sessionFactory = cache.getAdvancedCache().getComponentRegistry().getComponent(KeycloakSessionFactory.class);
|
KeycloakSessionFactory sessionFactory = workCache.getAdvancedCache().getComponentRegistry().getComponent(KeycloakSessionFactory.class);
|
||||||
if (sessionFactory == null) {
|
if (sessionFactory == null) {
|
||||||
log.warnf("KeycloakSessionFactory not yet set in cache. Worker skipped");
|
log.warnf("KeycloakSessionFactory not yet set in cache. Worker skipped");
|
||||||
return InfinispanUserSessionInitializer.WorkerResult.create(segment, false);
|
return InfinispanUserSessionInitializer.WorkerResult.create(segment, false);
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
*/
|
*/
|
||||||
public interface SessionLoader extends Serializable {
|
public interface SessionLoader extends Serializable {
|
||||||
|
|
||||||
void init(KeycloakSession session, int clusterStartupTime);
|
void init(KeycloakSession session);
|
||||||
|
|
||||||
int getSessionsCount(KeycloakSession session);
|
int getSessionsCount(KeycloakSession session);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory
|
|
@ -73,5 +73,9 @@
|
||||||
<dropUniqueConstraint tableName="FED_PROVIDERS" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" />
|
<dropUniqueConstraint tableName="FED_PROVIDERS" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" />
|
||||||
<dropTable tableName="FED_PROVIDERS" />
|
<dropTable tableName="FED_PROVIDERS" />
|
||||||
|
|
||||||
|
<createIndex indexName="IDX_US_SESS_ID_ON_CL_SESS" tableName="OFFLINE_CLIENT_SESSION">
|
||||||
|
<column name="USER_SESSION_ID" type="VARCHAR(36)"/>
|
||||||
|
</createIndex>
|
||||||
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
|
@ -18,6 +18,7 @@ package org.keycloak.saml.processing.core.util;
|
||||||
|
|
||||||
import org.keycloak.saml.common.PicketLinkLogger;
|
import org.keycloak.saml.common.PicketLinkLogger;
|
||||||
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.constants.WSTrustConstants;
|
import org.keycloak.saml.common.constants.WSTrustConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
@ -376,7 +377,19 @@ public class XMLSignatureUtil {
|
||||||
if (publicKey == null)
|
if (publicKey == null)
|
||||||
throw logger.nullValueError("Public Key");
|
throw logger.nullValueError("Public Key");
|
||||||
|
|
||||||
|
int signedAssertions = 0;
|
||||||
|
String assertionNameSpaceUri = null;
|
||||||
|
|
||||||
for (int i = 0; i < nl.getLength(); i++) {
|
for (int i = 0; i < nl.getLength(); i++) {
|
||||||
|
Node signatureNode = nl.item(i);
|
||||||
|
Node parent = signatureNode.getParentNode();
|
||||||
|
if (parent != null && JBossSAMLConstants.ASSERTION.get().equals(parent.getLocalName())) {
|
||||||
|
++signedAssertions;
|
||||||
|
if (assertionNameSpaceUri == null) {
|
||||||
|
assertionNameSpaceUri = parent.getNamespaceURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(i));
|
DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(i));
|
||||||
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
|
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
|
||||||
|
|
||||||
|
@ -397,6 +410,16 @@ public class XMLSignatureUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NodeList assertions = signedDoc.getElementsByTagNameNS(assertionNameSpaceUri, JBossSAMLConstants.ASSERTION.get());
|
||||||
|
|
||||||
|
if (signedAssertions > 0 && assertions != null && assertions.getLength() != signedAssertions) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("SAML Response document may contain malicious assertions. Signature validation will fail.");
|
||||||
|
}
|
||||||
|
// there are unsigned assertions mixed with signed ones
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClusterEvent extends Serializable {
|
||||||
|
}
|
|
@ -15,20 +15,20 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan.initializer;
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Task to be executed on all cluster nodes once it's notified.
|
||||||
|
*
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class TimeAwareInitializerState extends InitializerState {
|
public interface ClusterListener {
|
||||||
|
|
||||||
private int clusterStartupTime;
|
/**
|
||||||
|
* Registered task to be executed on all cluster nodes once it's notified from cache.
|
||||||
|
*
|
||||||
|
* @param event value of notification (Object added into the cache)
|
||||||
|
*/
|
||||||
|
void run(ClusterEvent event);
|
||||||
|
|
||||||
public int getClusterStartupTime() {
|
|
||||||
return clusterStartupTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClusterStartupTime(int clusterStartupTime) {
|
|
||||||
this.clusterStartupTime = clusterStartupTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various utils related to clustering and concurrent tasks on cluster nodes
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClusterProvider extends Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same value for all cluster nodes. It will use startup time of this server in non-cluster environment.
|
||||||
|
*/
|
||||||
|
int getClusterStartupTime();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute given task just if it's not already in progress (either on this or any other cluster node).
|
||||||
|
*
|
||||||
|
* @param taskKey
|
||||||
|
* @param taskTimeoutInSeconds timeout for given task. If there is existing task in progress for longer time, it's considered outdated so we will start our task.
|
||||||
|
* @param task
|
||||||
|
* @param <T>
|
||||||
|
* @return result with "executed" flag specifying if execution was executed or ignored.
|
||||||
|
*/
|
||||||
|
<T> ExecutionResult<T> executeIfNotExecuted(String taskKey, int taskTimeoutInSeconds, Callable<T> task);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register task (listener) under given key. When this key will be put to the cache on any cluster node, the task will be executed
|
||||||
|
*
|
||||||
|
* @param taskKey
|
||||||
|
* @param task
|
||||||
|
*/
|
||||||
|
void registerListener(String taskKey, ClusterListener task);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify registered listeners on all cluster nodes
|
||||||
|
*
|
||||||
|
* @param taskKey
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
void notify(String taskKey, ClusterEvent event);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClusterProviderFactory extends ProviderFactory<ClusterProvider> {
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClusterSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "cluster";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return ClusterProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return ClusterProviderFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.cluster;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ExecutionResult<T> {
|
||||||
|
|
||||||
|
private final boolean executed;
|
||||||
|
private final T result;
|
||||||
|
|
||||||
|
private ExecutionResult(boolean executed, T result) {
|
||||||
|
this.executed = executed;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ExecutionResult<T> executed(T result) {
|
||||||
|
return new ExecutionResult<>(true, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ExecutionResult<T> notExecuted() {
|
||||||
|
return new ExecutionResult<>(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExecuted() {
|
||||||
|
return executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,11 +22,21 @@ package org.keycloak.models;
|
||||||
*/
|
*/
|
||||||
public class UserFederationSyncResult {
|
public class UserFederationSyncResult {
|
||||||
|
|
||||||
|
private boolean ignored;
|
||||||
|
|
||||||
private int added;
|
private int added;
|
||||||
private int updated;
|
private int updated;
|
||||||
private int removed;
|
private int removed;
|
||||||
private int failed;
|
private int failed;
|
||||||
|
|
||||||
|
public boolean isIgnored() {
|
||||||
|
return ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnored(boolean ignored) {
|
||||||
|
this.ignored = ignored;
|
||||||
|
}
|
||||||
|
|
||||||
public int getAdded() {
|
public int getAdded() {
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
@ -83,11 +93,15 @@ public class UserFederationSyncResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStatus() {
|
public String getStatus() {
|
||||||
String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
|
if (ignored) {
|
||||||
if (failed != 0) {
|
return "Synchronization ignored as it's already in progress";
|
||||||
status += String.format(", %d users failed sync! See server log for more details", failed);
|
} else {
|
||||||
|
String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
|
||||||
|
if (failed != 0) {
|
||||||
|
status += String.format(", %d users failed sync! See server log for more details", failed);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,4 +112,10 @@ public class UserFederationSyncResult {
|
||||||
public static UserFederationSyncResult empty() {
|
public static UserFederationSyncResult empty() {
|
||||||
return new UserFederationSyncResult();
|
return new UserFederationSyncResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UserFederationSyncResult ignored() {
|
||||||
|
UserFederationSyncResult result = new UserFederationSyncResult();
|
||||||
|
result.setIgnored(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,9 +82,6 @@ public interface UserSessionProvider extends Provider {
|
||||||
void removeClientInitialAccessModel(RealmModel realm, String id);
|
void removeClientInitialAccessModel(RealmModel realm, String id);
|
||||||
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
|
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
|
||||||
|
|
||||||
// Will use startup time of this server in non-cluster environment
|
|
||||||
int getClusterStartupTime();
|
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,5 +49,6 @@ org.keycloak.authentication.ClientAuthenticatorSpi
|
||||||
org.keycloak.authentication.RequiredActionSpi
|
org.keycloak.authentication.RequiredActionSpi
|
||||||
org.keycloak.authentication.FormAuthenticatorSpi
|
org.keycloak.authentication.FormAuthenticatorSpi
|
||||||
org.keycloak.authentication.FormActionSpi
|
org.keycloak.authentication.FormActionSpi
|
||||||
|
org.keycloak.cluster.ClusterSpi
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
*/
|
*/
|
||||||
public class PartialImportResult {
|
public class PartialImportResult {
|
||||||
|
|
||||||
private final Action action;
|
private Action action;
|
||||||
private final ResourceType resourceType;
|
private ResourceType resourceType;
|
||||||
private final String resourceName;
|
private String resourceName;
|
||||||
private final String id;
|
private String id;
|
||||||
private final Object representation;
|
private Object representation;
|
||||||
|
|
||||||
|
private PartialImportResult() {};
|
||||||
|
|
||||||
private PartialImportResult(Action action, ResourceType resourceType, String resourceName, String id, Object representation) {
|
private PartialImportResult(Action action, ResourceType resourceType, String resourceName, String id, Object representation) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
|
@ -56,18 +58,34 @@ public class PartialImportResult {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAction(Action action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceType getResourceType() {
|
public ResourceType getResourceType() {
|
||||||
return resourceType;
|
return resourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceType(ResourceType resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getResourceName() {
|
public String getResourceName() {
|
||||||
return resourceName;
|
return resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceName(String resourceName) {
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Object getRepresentation() {
|
public Object getRepresentation() {
|
||||||
return representation;
|
return representation;
|
||||||
|
|
|
@ -28,6 +28,14 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class PartialImportResults {
|
public class PartialImportResults {
|
||||||
|
|
||||||
|
// these fields used only for marsalling from JSON with admin client
|
||||||
|
// they are never directly set
|
||||||
|
private int overwritten;
|
||||||
|
private int added;
|
||||||
|
private int skipped;
|
||||||
|
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
private final Set<PartialImportResult> importResults = new HashSet<>();
|
private final Set<PartialImportResult> importResults = new HashSet<>();
|
||||||
|
|
||||||
public void addResult(PartialImportResult result) {
|
public void addResult(PartialImportResult result) {
|
||||||
|
@ -68,4 +76,13 @@ public class PartialImportResults {
|
||||||
public Set<PartialImportResult> getResults() {
|
public Set<PartialImportResult> getResults() {
|
||||||
return importResults;
|
return importResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.protocol.oidc;
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
|
@ -193,7 +194,7 @@ public class TokenManager {
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
if (realm.isRevokeRefreshToken()) {
|
if (realm.isRevokeRefreshToken()) {
|
||||||
int clusterStartupTime = session.sessions().getClusterStartupTime();
|
int clusterStartupTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
|
||||||
|
|
||||||
if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (clusterStartupTime != validation.clientSession.getTimestamp())) {
|
if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (clusterStartupTime != validation.clientSession.getTimestamp())) {
|
||||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||||
|
|
|
@ -231,7 +231,7 @@ public class RealmManager implements RealmImporter {
|
||||||
// Remove all periodic syncs for configured federation providers
|
// Remove all periodic syncs for configured federation providers
|
||||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||||
usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), fedProvider);
|
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, fedProvider, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removed;
|
return removed;
|
||||||
|
@ -434,7 +434,7 @@ public class RealmManager implements RealmImporter {
|
||||||
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
||||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||||
usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
|
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, fedProvider, false);
|
||||||
}
|
}
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import org.keycloak.cluster.ClusterEvent;
|
||||||
|
import org.keycloak.cluster.ClusterListener;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.cluster.ExecutionResult;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -30,13 +34,17 @@ import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class UsersSyncManager {
|
public class UsersSyncManager {
|
||||||
|
|
||||||
|
private static final String FEDERATION_TASK_KEY = "federation";
|
||||||
|
|
||||||
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,26 +65,106 @@ public class UsersSyncManager {
|
||||||
refreshPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId());
|
refreshPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
|
||||||
|
clusterProvider.registerListener(FEDERATION_TASK_KEY, new UserFederationClusterListener(sessionFactory));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserFederationSyncResult syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
|
|
||||||
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
|
private class Holder {
|
||||||
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
|
ExecutionResult<UserFederationSyncResult> result;
|
||||||
return fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserFederationSyncResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
|
public UserFederationSyncResult syncAllUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedProvider) {
|
||||||
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
|
final Holder holder = new Holder();
|
||||||
|
|
||||||
// See when we did last sync.
|
// Ensure not executed concurrently on this or any other cluster node
|
||||||
int oldLastSync = fedProvider.getLastSync();
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
|
|
||||||
return fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
|
||||||
|
// shared key for "full" and "changed" . Improve if needed
|
||||||
|
String taskKey = fedProvider.getId() + "::sync";
|
||||||
|
|
||||||
|
// 30 seconds minimal timeout for now
|
||||||
|
int timeout = Math.max(30, fedProvider.getFullSyncPeriod());
|
||||||
|
holder.result = clusterProvider.executeIfNotExecuted(taskKey, timeout, new Callable<UserFederationSyncResult>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserFederationSyncResult call() throws Exception {
|
||||||
|
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
|
||||||
|
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
|
||||||
|
return fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (holder.result == null || !holder.result.isExecuted()) {
|
||||||
|
logger.debugf("syncAllUsers for federation provider %s was ignored as it's already in progress", fedProvider.getDisplayName());
|
||||||
|
return UserFederationSyncResult.ignored();
|
||||||
|
} else {
|
||||||
|
return holder.result.getResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
|
public UserFederationSyncResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedProvider) {
|
||||||
|
final Holder holder = new Holder();
|
||||||
|
|
||||||
|
// Ensure not executed concurrently on this or any other cluster node
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
|
||||||
|
// shared key for "full" and "changed" . Improve if needed
|
||||||
|
String taskKey = fedProvider.getId() + "::sync";
|
||||||
|
|
||||||
|
// 30 seconds minimal timeout for now
|
||||||
|
int timeout = Math.max(30, fedProvider.getChangedSyncPeriod());
|
||||||
|
holder.result = clusterProvider.executeIfNotExecuted(taskKey, timeout, new Callable<UserFederationSyncResult>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserFederationSyncResult call() throws Exception {
|
||||||
|
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
|
||||||
|
|
||||||
|
// See when we did last sync.
|
||||||
|
int oldLastSync = fedProvider.getLastSync();
|
||||||
|
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
|
||||||
|
return fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (holder.result == null || !holder.result.isExecuted()) {
|
||||||
|
logger.debugf("syncChangedUsers for federation provider %s was ignored as it's already in progress", fedProvider.getDisplayName());
|
||||||
|
return UserFederationSyncResult.ignored();
|
||||||
|
} else {
|
||||||
|
return holder.result.getResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure all cluster nodes are notified
|
||||||
|
public void notifyToRefreshPeriodicSync(KeycloakSession session, RealmModel realm, UserFederationProviderModel federationProvider, boolean removed) {
|
||||||
|
FederationProviderClusterEvent event = FederationProviderClusterEvent.createEvent(removed, realm.getId(), federationProvider);
|
||||||
|
session.getProvider(ClusterProvider.class).notify(FEDERATION_TASK_KEY, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Executed once it receives notification that some UserFederationProvider was created or updated
|
||||||
|
protected void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
|
||||||
|
logger.debugf("Going to refresh periodic sync for provider '%s' . Full sync period: %d , changed users sync period: %d",
|
||||||
|
fedProvider.getDisplayName(), fedProvider.getFullSyncPeriod(), fedProvider.getChangedSyncPeriod());
|
||||||
|
|
||||||
if (fedProvider.getFullSyncPeriod() > 0) {
|
if (fedProvider.getFullSyncPeriod() > 0) {
|
||||||
// We want periodic full sync for this provider
|
// We want periodic full sync for this provider
|
||||||
timer.schedule(new Runnable() {
|
timer.schedule(new Runnable() {
|
||||||
|
@ -84,7 +172,12 @@ public class UsersSyncManager {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
syncAllUsers(sessionFactory, realmId, fedProvider);
|
boolean shouldPerformSync = shouldPerformNewPeriodicSync(fedProvider.getLastSync(), fedProvider.getChangedSyncPeriod());
|
||||||
|
if (shouldPerformSync) {
|
||||||
|
syncAllUsers(sessionFactory, realmId, fedProvider);
|
||||||
|
} else {
|
||||||
|
logger.debugf("Ignored periodic full sync with federation provider %s due small time since last sync", fedProvider.getDisplayName());
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.errorDuringFullUserSync(t);
|
logger.errorDuringFullUserSync(t);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +195,12 @@ public class UsersSyncManager {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
syncChangedUsers(sessionFactory, realmId, fedProvider);
|
boolean shouldPerformSync = shouldPerformNewPeriodicSync(fedProvider.getLastSync(), fedProvider.getChangedSyncPeriod());
|
||||||
|
if (shouldPerformSync) {
|
||||||
|
syncChangedUsers(sessionFactory, realmId, fedProvider);
|
||||||
|
} else {
|
||||||
|
logger.debugf("Ignored periodic changed-users sync with federation provider %s due small time since last sync", fedProvider.getDisplayName());
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.errorDuringChangedUserSync(t);
|
logger.errorDuringChangedUserSync(t);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +213,21 @@ public class UsersSyncManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePeriodicSyncForProvider(TimerProvider timer, final UserFederationProviderModel fedProvider) {
|
// Skip syncing if there is short time since last sync time.
|
||||||
|
private boolean shouldPerformNewPeriodicSync(int lastSyncTime, int period) {
|
||||||
|
if (lastSyncTime <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
int timeSinceLastSync = currentTime - lastSyncTime;
|
||||||
|
|
||||||
|
return (timeSinceLastSync * 2 > period);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executed once it receives notification that some UserFederationProvider was removed
|
||||||
|
protected void removePeriodicSyncForProvider(TimerProvider timer, UserFederationProviderModel fedProvider) {
|
||||||
|
logger.debugf("Removing periodic sync for provider %s", fedProvider.getDisplayName());
|
||||||
timer.cancelTask(fedProvider.getId() + "-FULL");
|
timer.cancelTask(fedProvider.getId() + "-FULL");
|
||||||
timer.cancelTask(fedProvider.getId() + "-CHANGED");
|
timer.cancelTask(fedProvider.getId() + "-CHANGED");
|
||||||
}
|
}
|
||||||
|
@ -144,4 +256,73 @@ public class UsersSyncManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class UserFederationClusterListener implements ClusterListener {
|
||||||
|
|
||||||
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
|
|
||||||
|
public UserFederationClusterListener(KeycloakSessionFactory sessionFactory) {
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ClusterEvent event) {
|
||||||
|
final FederationProviderClusterEvent fedEvent = (FederationProviderClusterEvent) event;
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||||
|
if (fedEvent.isRemoved()) {
|
||||||
|
removePeriodicSyncForProvider(timer, fedEvent.getFederationProvider());
|
||||||
|
} else {
|
||||||
|
refreshPeriodicSyncForProvider(sessionFactory, timer, fedEvent.getFederationProvider(), fedEvent.getRealmId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send to cluster during each update or remove of federationProvider, so all nodes can update sync periods
|
||||||
|
public static class FederationProviderClusterEvent implements ClusterEvent {
|
||||||
|
|
||||||
|
private boolean removed;
|
||||||
|
private String realmId;
|
||||||
|
private UserFederationProviderModel federationProvider;
|
||||||
|
|
||||||
|
public boolean isRemoved() {
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoved(boolean removed) {
|
||||||
|
this.removed = removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserFederationProviderModel getFederationProvider() {
|
||||||
|
return federationProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFederationProvider(UserFederationProviderModel federationProvider) {
|
||||||
|
this.federationProvider = federationProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FederationProviderClusterEvent createEvent(boolean removed, String realmId, UserFederationProviderModel fedProvider) {
|
||||||
|
FederationProviderClusterEvent notification = new FederationProviderClusterEvent();
|
||||||
|
notification.setRemoved(removed);
|
||||||
|
notification.setRealmId(realmId);
|
||||||
|
notification.setFederationProvider(fedProvider);
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.services.resources.admin.AdminRoot;
|
import org.keycloak.services.resources.admin.AdminRoot;
|
||||||
import org.keycloak.services.scheduled.ClearExpiredEvents;
|
import org.keycloak.services.scheduled.ClearExpiredEvents;
|
||||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||||
|
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
|
||||||
import org.keycloak.services.scheduled.ScheduledTaskRunner;
|
import org.keycloak.services.scheduled.ScheduledTaskRunner;
|
||||||
import org.keycloak.services.util.JsonConfigProvider;
|
import org.keycloak.services.util.JsonConfigProvider;
|
||||||
import org.keycloak.services.util.ObjectMapperResolver;
|
import org.keycloak.services.util.ObjectMapperResolver;
|
||||||
|
@ -217,8 +218,8 @@ public class KeycloakApplication extends Application {
|
||||||
KeycloakSession session = sessionFactory.create();
|
KeycloakSession session = sessionFactory.create();
|
||||||
try {
|
try {
|
||||||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||||
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredEvents()), interval, "ClearExpiredEvents");
|
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
|
||||||
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
|
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions(), interval), interval, "ClearExpiredUserSessions");
|
||||||
new UsersSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
new UsersSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
||||||
} finally {
|
} finally {
|
||||||
session.close();
|
session.close();
|
||||||
|
|
|
@ -242,7 +242,7 @@ public class RealmAdminResource {
|
||||||
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
||||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||||
usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
|
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, fedProvider, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
adminEvent.operation(OperationType.UPDATE).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).representation(rep).success();
|
||||||
|
|
|
@ -106,7 +106,7 @@ public class UserFederationProviderResource {
|
||||||
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
||||||
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
||||||
realm.updateUserFederationProvider(model);
|
realm.updateUserFederationProvider(model);
|
||||||
new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
||||||
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
|
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
|
||||||
if (kerberosCredsAdded) {
|
if (kerberosCredsAdded) {
|
||||||
logger.addedKerberosToRealmCredentials();
|
logger.addedKerberosToRealmCredentials();
|
||||||
|
@ -138,7 +138,7 @@ public class UserFederationProviderResource {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
realm.removeUserFederationProvider(this.federationProviderModel);
|
realm.removeUserFederationProvider(this.federationProviderModel);
|
||||||
new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), this.federationProviderModel);
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, this.federationProviderModel, true);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class UserFederationProvidersResource {
|
||||||
}
|
}
|
||||||
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
||||||
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
||||||
new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
||||||
boolean kerberosCredsAdded = checkKerberosCredential(session, realm, model);
|
boolean kerberosCredsAdded = checkKerberosCredential(session, realm, model);
|
||||||
if (kerberosCredsAdded) {
|
if (kerberosCredsAdded) {
|
||||||
logger.addedKerberosToRealmCredentials();
|
logger.addedKerberosToRealmCredentials();
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.services.scheduled;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.cluster.ExecutionResult;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that there are not concurrent executions of same task (either on this host or any other cluster host)
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClusterAwareScheduledTaskRunner extends ScheduledTaskRunner {
|
||||||
|
|
||||||
|
private final int intervalSecs;
|
||||||
|
|
||||||
|
public ClusterAwareScheduledTaskRunner(KeycloakSessionFactory sessionFactory, ScheduledTask task, long intervalMillis) {
|
||||||
|
super(sessionFactory, task);
|
||||||
|
this.intervalSecs = (int) (intervalMillis / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runTask(final KeycloakSession session) {
|
||||||
|
session.getTransaction().begin();
|
||||||
|
|
||||||
|
ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
|
||||||
|
String taskKey = task.getClass().getSimpleName();
|
||||||
|
|
||||||
|
ExecutionResult<Void> result = clusterProvider.executeIfNotExecuted(taskKey, intervalSecs, new Callable<Void>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void call() throws Exception {
|
||||||
|
task.run(session);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
if (result.isExecuted()) {
|
||||||
|
logger.debugf("Executed scheduled task %s", taskKey);
|
||||||
|
} else {
|
||||||
|
logger.debugf("Skipped execution of task %s as other cluster node is executing it", taskKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.services.scheduled;
|
package org.keycloak.services.scheduled;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.cluster.ExecutionResult;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
@ -26,10 +30,10 @@ import org.keycloak.services.ServicesLogger;
|
||||||
*/
|
*/
|
||||||
public class ScheduledTaskRunner implements Runnable {
|
public class ScheduledTaskRunner implements Runnable {
|
||||||
|
|
||||||
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
protected final KeycloakSessionFactory sessionFactory;
|
||||||
private final ScheduledTask task;
|
protected final ScheduledTask task;
|
||||||
|
|
||||||
public ScheduledTaskRunner(KeycloakSessionFactory sessionFactory, ScheduledTask task) {
|
public ScheduledTaskRunner(KeycloakSessionFactory sessionFactory, ScheduledTask task) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
@ -40,11 +44,7 @@ public class ScheduledTaskRunner implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
KeycloakSession session = sessionFactory.create();
|
KeycloakSession session = sessionFactory.create();
|
||||||
try {
|
try {
|
||||||
session.getTransaction().begin();
|
runTask(session);
|
||||||
task.run(session);
|
|
||||||
session.getTransaction().commit();
|
|
||||||
|
|
||||||
logger.debug("Executed scheduled task " + task.getClass().getSimpleName());
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.failedToRunScheduledTask(t, task.getClass().getSimpleName());
|
logger.failedToRunScheduledTask(t, task.getClass().getSimpleName());
|
||||||
|
|
||||||
|
@ -58,4 +58,12 @@ public class ScheduledTaskRunner implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void runTask(KeycloakSession session) {
|
||||||
|
session.getTransaction().begin();
|
||||||
|
task.run(session);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
logger.debug("Executed scheduled task " + task.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.theme;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -42,7 +43,7 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
public ExtendingThemeManager(KeycloakSession session, ConcurrentHashMap<ExtendingThemeManagerFactory.ThemeKey, Theme> themeCache) {
|
public ExtendingThemeManager(KeycloakSession session, ConcurrentHashMap<ExtendingThemeManagerFactory.ThemeKey, Theme> themeCache) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.themeCache = themeCache;
|
this.themeCache = themeCache;
|
||||||
this.defaultTheme = Config.scope("theme").get("default", "keycloak");
|
this.defaultTheme = Config.scope("theme").get("default", Version.NAME.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ThemeProvider> getProviders() {
|
private List<ThemeProvider> getProviders() {
|
||||||
|
|
|
@ -415,6 +415,11 @@
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>auth-server-wildfly-cluster</id>
|
<id>auth-server-wildfly-cluster</id>
|
||||||
|
<properties>
|
||||||
|
<session.cache.owners>1</session.cache.owners>
|
||||||
|
<offline.session.cache.owners>1</offline.session.cache.owners>
|
||||||
|
<login.failure.cache.owners>1</login.failure.cache.owners>
|
||||||
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
@ -448,6 +453,28 @@
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
</transformationSet>
|
</transformationSet>
|
||||||
|
<transformationSet>
|
||||||
|
<dir>${keycloak.server.home}/standalone/configuration</dir>
|
||||||
|
<includes>
|
||||||
|
<include>standalone-ha.xml</include>
|
||||||
|
</includes>
|
||||||
|
<stylesheet>src/main/xslt/ispn-cache-owners.xsl</stylesheet>
|
||||||
|
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
|
||||||
|
<parameters>
|
||||||
|
<parameter>
|
||||||
|
<name>sessionCacheOwners</name>
|
||||||
|
<value>${session.cache.owners}</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>offlineSessionCacheOwners</name>
|
||||||
|
<value>${offline.session.cache.owners}</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>loginFailureCacheOwners</name>
|
||||||
|
<value>${login.failure.cache.owners}</value>
|
||||||
|
</parameter>
|
||||||
|
</parameters>
|
||||||
|
</transformationSet>
|
||||||
</transformationSets>
|
</transformationSets>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:xalan="http://xml.apache.org/xalan"
|
||||||
|
xmlns:j="urn:jboss:domain:4.0"
|
||||||
|
xmlns:i="urn:jboss:domain:infinispan:4.0"
|
||||||
|
version="2.0"
|
||||||
|
exclude-result-prefixes="xalan i">
|
||||||
|
|
||||||
|
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||||
|
<xsl:strip-space elements="*"/>
|
||||||
|
|
||||||
|
<xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
|
||||||
|
|
||||||
|
<xsl:param name="sessionCacheOwners" select="'1'"/>
|
||||||
|
<xsl:param name="offlineSessionCacheOwners" select="'1'"/>
|
||||||
|
<xsl:param name="loginFailureCacheOwners" select="'1'"/>
|
||||||
|
|
||||||
|
<xsl:template match="//i:cache-container/i:distributed-cache[@name='sessions']/@owners">
|
||||||
|
<xsl:attribute name="owners">
|
||||||
|
<xsl:value-of select="$sessionCacheOwners"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:template>
|
||||||
|
<xsl:template match="//i:cache-container/i:distributed-cache[@name='offlineSessions']/@owners">
|
||||||
|
<xsl:attribute name="owners">
|
||||||
|
<xsl:value-of select="$offlineSessionCacheOwners"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:template>
|
||||||
|
<xsl:template match="//i:cache-container/i:distributed-cache[@name='loginFailures']/@owners">
|
||||||
|
<xsl:attribute name="owners">
|
||||||
|
<xsl:value-of select="$loginFailureCacheOwners"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Copy everything else. -->
|
||||||
|
<xsl:template match="@*|node()">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="@*|node()" />
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -29,7 +29,7 @@ import java.net.URL;
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
public class SAMLPostSigExample extends AbstractPageWithInjectedUrl {
|
public class SAMLPostSigExample extends AbstractPageWithInjectedUrl {
|
||||||
public static final String DEPLOYMENT_NAME = "saml-post-signatures";
|
public static final String DEPLOYMENT_NAME = "sales-post-sig";
|
||||||
|
|
||||||
@ArquillianResource
|
@ArquillianResource
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||||
|
|
|
@ -30,8 +30,10 @@ import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||||
|
|
||||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by st on 28.05.15.
|
* Created by st on 28.05.15.
|
||||||
|
@ -39,7 +41,7 @@ import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD
|
||||||
public class ApiUtil {
|
public class ApiUtil {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(ApiUtil.class);
|
private static final Logger log = Logger.getLogger(ApiUtil.class);
|
||||||
|
|
||||||
public static String getCreatedId(Response response) {
|
public static String getCreatedId(Response response) {
|
||||||
URI location = response.getLocation();
|
URI location = response.getLocation();
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
|
@ -48,7 +50,7 @@ public class ApiUtil {
|
||||||
String path = location.getPath();
|
String path = location.getPath();
|
||||||
return path.substring(path.lastIndexOf('/') + 1);
|
return path.substring(path.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientResource findClientResourceByClientId(RealmResource realm, String clientId) {
|
public static ClientResource findClientResourceByClientId(RealmResource realm, String clientId) {
|
||||||
for (ClientRepresentation c : realm.clients().findAll()) {
|
for (ClientRepresentation c : realm.clients().findAll()) {
|
||||||
if (c.getClientId().equals(clientId)) {
|
if (c.getClientId().equals(clientId)) {
|
||||||
|
@ -57,7 +59,7 @@ public class ApiUtil {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientResource findClientResourceByName(RealmResource realm, String name) {
|
public static ClientResource findClientResourceByName(RealmResource realm, String name) {
|
||||||
for (ClientRepresentation c : realm.clients().findAll()) {
|
for (ClientRepresentation c : realm.clients().findAll()) {
|
||||||
if (c.getName().equals(name)) {
|
if (c.getName().equals(name)) {
|
||||||
|
@ -66,7 +68,7 @@ public class ApiUtil {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientRepresentation findClientByClientId(RealmResource realm, String clientId) {
|
public static ClientRepresentation findClientByClientId(RealmResource realm, String clientId) {
|
||||||
ClientRepresentation client = null;
|
ClientRepresentation client = null;
|
||||||
for (ClientRepresentation c : realm.clients().findAll()) {
|
for (ClientRepresentation c : realm.clients().findAll()) {
|
||||||
|
@ -76,7 +78,7 @@ public class ApiUtil {
|
||||||
}
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserRepresentation findUserByUsername(RealmResource realm, String username) {
|
public static UserRepresentation findUserByUsername(RealmResource realm, String username) {
|
||||||
UserRepresentation user = null;
|
UserRepresentation user = null;
|
||||||
List<UserRepresentation> ur = realm.users().search(username, null, null);
|
List<UserRepresentation> ur = realm.users().search(username, null, null);
|
||||||
|
@ -85,7 +87,7 @@ public class ApiUtil {
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createUserWithAdminClient(RealmResource realm, UserRepresentation user) {
|
public static String createUserWithAdminClient(RealmResource realm, UserRepresentation user) {
|
||||||
Response response = realm.users().create(user);
|
Response response = realm.users().create(user);
|
||||||
String createdId = getCreatedId(response);
|
String createdId = getCreatedId(response);
|
||||||
|
@ -98,7 +100,7 @@ public class ApiUtil {
|
||||||
resetUserPassword(realm.users().get(id), password, false);
|
resetUserPassword(realm.users().get(id), password, false);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resetUserPassword(UserResource userResource, String newPassword, boolean temporary) {
|
public static void resetUserPassword(UserResource userResource, String newPassword, boolean temporary) {
|
||||||
CredentialRepresentation newCredential = new CredentialRepresentation();
|
CredentialRepresentation newCredential = new CredentialRepresentation();
|
||||||
newCredential.setType(PASSWORD);
|
newCredential.setType(PASSWORD);
|
||||||
|
@ -106,7 +108,7 @@ public class ApiUtil {
|
||||||
newCredential.setTemporary(temporary);
|
newCredential.setTemporary(temporary);
|
||||||
userResource.resetPassword(newCredential);
|
userResource.resetPassword(newCredential);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assignClientRoles(RealmResource realm, String userId, String clientName, String... roles) {
|
public static void assignClientRoles(RealmResource realm, String userId, String clientName, String... roles) {
|
||||||
String realmName = realm.toRepresentation().getRealm();
|
String realmName = realm.toRepresentation().getRealm();
|
||||||
String clientId = "";
|
String clientId = "";
|
||||||
|
@ -118,21 +120,32 @@ public class ApiUtil {
|
||||||
|
|
||||||
if (!clientId.isEmpty()) {
|
if (!clientId.isEmpty()) {
|
||||||
ClientResource clientResource = realm.clients().get(clientId);
|
ClientResource clientResource = realm.clients().get(clientId);
|
||||||
|
|
||||||
List<RoleRepresentation> roleRepresentations = new ArrayList<>();
|
List<RoleRepresentation> roleRepresentations = new ArrayList<>();
|
||||||
for (String roleName : roles) {
|
for (String roleName : roles) {
|
||||||
RoleRepresentation role = clientResource.roles().get(roleName).toRepresentation();
|
RoleRepresentation role = clientResource.roles().get(roleName).toRepresentation();
|
||||||
roleRepresentations.add(role);
|
roleRepresentations.add(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserResource userResource = realm.users().get(userId);
|
UserResource userResource = realm.users().get(userId);
|
||||||
log.debug("assigning roles: " + Arrays.toString(roles) + " to user: \"" +
|
log.debug("assigning roles: " + Arrays.toString(roles) + " to user: \""
|
||||||
userResource.toRepresentation().getUsername() + "\" of client: \"" +
|
+ userResource.toRepresentation().getUsername() + "\" of client: \""
|
||||||
clientName + "\" in realm: \"" + realmName + "\"");
|
+ clientName + "\" in realm: \"" + realmName + "\"");
|
||||||
userResource.roles().clientLevel(clientId).add(roleRepresentations);
|
userResource.roles().clientLevel(clientId).add(roleRepresentations);
|
||||||
} else {
|
} else {
|
||||||
log.warn("client with name " + clientName + "doesn't exist in realm " + realmName);
|
log.warn("client with name " + clientName + "doesn't exist in realm " + realmName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean groupContainsSubgroup(GroupRepresentation group, GroupRepresentation subgroup) {
|
||||||
|
boolean contains = false;
|
||||||
|
for (GroupRepresentation sg : group.getSubGroups()) {
|
||||||
|
if (subgroup.getId().equals(sg.getId())) {
|
||||||
|
contains = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contains;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package org.keycloak.testsuite.arquillian;
|
package org.keycloak.testsuite.arquillian;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
|
import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
|
||||||
import org.jboss.arquillian.container.test.api.ContainerController;
|
import org.jboss.arquillian.container.test.api.ContainerController;
|
||||||
import org.jboss.arquillian.core.api.Instance;
|
import org.jboss.arquillian.core.api.Instance;
|
||||||
|
@ -13,12 +9,18 @@ import org.jboss.arquillian.core.api.annotation.Observes;
|
||||||
import org.jboss.arquillian.test.spi.annotation.ClassScoped;
|
import org.jboss.arquillian.test.spi.annotation.ClassScoped;
|
||||||
import org.jboss.arquillian.test.spi.event.suite.BeforeClass;
|
import org.jboss.arquillian.test.spi.event.suite.BeforeClass;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot;
|
|
||||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerQualifier;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
|
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
import static org.keycloak.testsuite.util.IOUtil.execCommand;
|
|
||||||
import org.keycloak.testsuite.util.LogChecker;
|
import org.keycloak.testsuite.util.LogChecker;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot;
|
||||||
|
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerQualifier;
|
||||||
|
import static org.keycloak.testsuite.util.IOUtil.execCommand;
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,7 +160,9 @@ public class AppServerTestEnricher {
|
||||||
execCommand(command + " --connect --command=reload" + controllerArg, bin);
|
execCommand(command + " --connect --command=reload" + controllerArg, bin);
|
||||||
log.info("Container restarted");
|
log.info("Container restarted");
|
||||||
pause(5000);
|
pause(5000);
|
||||||
LogChecker.checkJBossServerLog(jbossHomePath);
|
if (System.getProperty("app.server.log.check","true").equals("true")) {
|
||||||
|
LogChecker.checkJBossServerLog(jbossHomePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appServerInfo.setAdapterLibsInstalled(true);
|
appServerInfo.setAdapterLibsInstalled(true);
|
||||||
|
|
|
@ -35,11 +35,12 @@ public abstract class Login extends AuthRealm {
|
||||||
public static final String PROTOCOL = "protocol";
|
public static final String PROTOCOL = "protocol";
|
||||||
public static final String OIDC = "openid-connect";
|
public static final String OIDC = "openid-connect";
|
||||||
public static final String SAML = "saml";
|
public static final String SAML = "saml";
|
||||||
|
public static final String LOGIN_ACTION = "login-action";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UriBuilder createUriBuilder() {
|
public UriBuilder createUriBuilder() {
|
||||||
return super.createUriBuilder()
|
return super.createUriBuilder()
|
||||||
.path("protocol/{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
|
.path((getProtocol().equals(OIDC) || getProtocol().equals(SAML)) ? "protocol/" : "" + "{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProtocol(String protocol) {
|
public void setProtocol(String protocol) {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.keycloak.testsuite.auth.page.login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class SAMLPostLogin extends Login {
|
||||||
|
SAMLPostLogin() {
|
||||||
|
setProtocol(LOGIN_ACTION);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,8 @@ package org.keycloak.testsuite.auth.page.login;
|
||||||
/**
|
/**
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
public class SAMLLogin extends Login {
|
public class SAMLRedirectLogin extends Login {
|
||||||
SAMLLogin() {
|
SAMLRedirectLogin() {
|
||||||
setProtocol(SAML);
|
setProtocol(SAML);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,8 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||||
import org.keycloak.testsuite.auth.page.login.SAMLLogin;
|
import org.keycloak.testsuite.auth.page.login.SAMLPostLogin;
|
||||||
|
import org.keycloak.testsuite.auth.page.login.SAMLRedirectLogin;
|
||||||
import org.openqa.selenium.Cookie;
|
import org.openqa.selenium.Cookie;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
@ -47,7 +48,10 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
|
||||||
protected OIDCLogin testRealmLoginPage;
|
protected OIDCLogin testRealmLoginPage;
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
protected SAMLLogin testRealmSAMLLoginPage;
|
protected SAMLPostLogin testRealmSAMLPostLoginPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected SAMLRedirectLogin testRealmSAMLRedirectLoginPage;
|
||||||
|
|
||||||
protected UserRepresentation testUser;
|
protected UserRepresentation testUser;
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest {
|
||||||
masterUrl = client.getBaseUrl();
|
masterUrl = client.getBaseUrl();
|
||||||
}
|
}
|
||||||
masterUrl = masterUrl.replaceFirst(regex, replacement);
|
masterUrl = masterUrl.replaceFirst(regex, replacement);
|
||||||
client.setAdminUrl(masterUrl);
|
client.setAdminUrl(masterUrl + ((!masterUrl.endsWith("/saml")) ? "/saml" : ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,8 @@ public abstract class AbstractSAMLExampleAdapterTest extends AbstractExampleAdap
|
||||||
public void setDefaultPageUriParameters() {
|
public void setDefaultPageUriParameters() {
|
||||||
super.setDefaultPageUriParameters();
|
super.setDefaultPageUriParameters();
|
||||||
testRealmPage.setAuthRealm(SAMLDEMO);
|
testRealmPage.setAuthRealm(SAMLDEMO);
|
||||||
testRealmSAMLLoginPage.setAuthRealm(SAMLDEMO);
|
testRealmSAMLRedirectLoginPage.setAuthRealm(SAMLDEMO);
|
||||||
|
testRealmSAMLPostLoginPage.setAuthRealm(SAMLDEMO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deployment(name = SAMLPostSigExample.DEPLOYMENT_NAME)
|
@Deployment(name = SAMLPostSigExample.DEPLOYMENT_NAME)
|
||||||
|
@ -81,41 +82,41 @@ public abstract class AbstractSAMLExampleAdapterTest extends AbstractExampleAdap
|
||||||
@Test
|
@Test
|
||||||
public void samlPostWithSignatureExampleTest() {
|
public void samlPostWithSignatureExampleTest() {
|
||||||
samlPostSigExamplePage.navigateTo();
|
samlPostSigExamplePage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
|
|
||||||
assertTrue(driver.getPageSource().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()));
|
assertTrue(driver.getPageSource().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()));
|
||||||
|
|
||||||
samlPostSigExamplePage.logout();
|
samlPostSigExamplePage.logout();
|
||||||
|
|
||||||
samlPostSigExamplePage.navigateTo();
|
samlPostSigExamplePage.navigateTo();
|
||||||
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void samlPostWithEncryptionExampleTest() {
|
public void samlPostWithEncryptionExampleTest() {
|
||||||
samlPostEncExamplePage.navigateTo();
|
samlPostEncExamplePage.navigateTo();
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
|
|
||||||
assertTrue(driver.getPageSource().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()));
|
assertTrue(driver.getPageSource().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()));
|
||||||
|
|
||||||
samlPostEncExamplePage.logout();
|
samlPostEncExamplePage.logout();
|
||||||
|
|
||||||
samlPostEncExamplePage.navigateTo();
|
samlPostEncExamplePage.navigateTo();
|
||||||
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void samlRedirectWithSignatureExampleTest() {
|
public void samlRedirectWithSignatureExampleTest() {
|
||||||
samlRedirectSigExamplePage.navigateTo();
|
samlRedirectSigExamplePage.navigateTo();
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
|
|
||||||
assertTrue(driver.getPageSource().contains("Welcome to the Employee Tool,"));
|
assertTrue(driver.getPageSource().contains("Welcome to the Employee Tool,"));
|
||||||
|
|
||||||
samlRedirectSigExamplePage.logout();
|
samlRedirectSigExamplePage.logout();
|
||||||
|
|
||||||
samlRedirectSigExamplePage.navigateTo();
|
samlRedirectSigExamplePage.navigateTo();
|
||||||
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,7 @@ import org.w3c.dom.Document;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
|
import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
|
||||||
import static org.keycloak.testsuite.util.IOUtil.*;
|
import static org.keycloak.testsuite.util.IOUtil.*;
|
||||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||||
|
@ -157,7 +155,8 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
public void setDefaultPageUriParameters() {
|
public void setDefaultPageUriParameters() {
|
||||||
super.setDefaultPageUriParameters();
|
super.setDefaultPageUriParameters();
|
||||||
testRealmPage.setAuthRealm(SAMLSERVLETDEMO);
|
testRealmPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
testRealmSAMLLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
testRealmSAMLRedirectLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
|
testRealmSAMLPostLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -177,7 +176,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void unauthorizedSSOTest() {
|
public void unauthorizedSSOTest() {
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
|
||||||
|
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -200,7 +199,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void singleLoginAndLogoutSAMLTest() {
|
public void singleLoginAndLogoutSAMLTest() {
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesPostSigServletPage.navigateTo();
|
salesPostSigServletPage.navigateTo();
|
||||||
|
@ -215,16 +214,16 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
employeeSigFrontServletPage.logout();
|
employeeSigFrontServletPage.logout();
|
||||||
|
|
||||||
employeeSigFrontServletPage.navigateTo();
|
employeeSigFrontServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
|
|
||||||
employeeSigServletPage.navigateTo();
|
employeeSigServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
|
|
||||||
salesPostPassiveServletPage.navigateTo();
|
salesPostPassiveServletPage.navigateTo();
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body><pre></pre></body>"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
|
||||||
|
|
||||||
salesPostSigEmailServletPage.navigateTo();
|
salesPostSigEmailServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -236,7 +235,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void badRealmSalesPostSigTest() {
|
public void badRealmSalesPostSigTest() {
|
||||||
badRealmSalesPostSigServletPage.navigateTo();
|
badRealmSalesPostSigServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
|
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -245,14 +244,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void employee2Test() {
|
public void employee2Test() {
|
||||||
employee2ServletPage.navigateTo();
|
employee2ServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
employee2ServletPage.logout();
|
employee2ServletPage.logout();
|
||||||
employee2ServletPage.navigateTo();
|
employee2ServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -262,14 +261,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void employeeSigTest() {
|
public void employeeSigTest() {
|
||||||
employeeSigServletPage.navigateTo();
|
employeeSigServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
employeeSigServletPage.logout();
|
employeeSigServletPage.logout();
|
||||||
employeeSigServletPage.navigateTo();
|
employeeSigServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -279,14 +278,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void employeeSigFrontTest() {
|
public void employeeSigFrontTest() {
|
||||||
employeeSigFrontServletPage.navigateTo();
|
employeeSigFrontServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
employeeSigFrontServletPage.logout();
|
employeeSigFrontServletPage.logout();
|
||||||
employeeSigFrontServletPage.navigateTo();
|
employeeSigFrontServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -301,19 +300,29 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
modifyDocElementAttribute(doc, "AssertionConsumerService", "Location", "8080", System.getProperty("app.server.http.port", null));
|
modifyDocElementAttribute(doc, "AssertionConsumerService", "Location", "8080", System.getProperty("app.server.http.port", null));
|
||||||
|
|
||||||
ClientRepresentation clientRep = testRealmResource().convertClientDescription(IOUtil.documentToString(doc));
|
ClientRepresentation clientRep = testRealmResource().convertClientDescription(IOUtil.documentToString(doc));
|
||||||
|
|
||||||
|
String appServerUrl;
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("app.server.ssl.required"))) {
|
||||||
|
appServerUrl = "https://localhost:" + System.getProperty("app.server.https.port", "8543") + "/";
|
||||||
|
} else {
|
||||||
|
appServerUrl = "http://localhost:" + System.getProperty("app.server.http.port", "8280") + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
clientRep.setAdminUrl(appServerUrl + "sales-metadata/saml");
|
||||||
|
|
||||||
Response response = testRealmResource().clients().create(clientRep);
|
Response response = testRealmResource().clients().create(clientRep);
|
||||||
assertEquals(201, response.getStatus());
|
assertEquals(201, response.getStatus());
|
||||||
response.close();
|
response.close();
|
||||||
|
|
||||||
salesMetadataServletPage.navigateTo();
|
salesMetadataServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesMetadataServletPage.logout();
|
salesMetadataServletPage.logout();
|
||||||
salesMetadataServletPage.navigateTo();
|
salesMetadataServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -323,14 +332,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostTest() {
|
public void salesPostTest() {
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesPostServletPage.logout();
|
salesPostServletPage.logout();
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -340,14 +349,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostEncTest() {
|
public void salesPostEncTest() {
|
||||||
salesPostEncServletPage.navigateTo();
|
salesPostEncServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesPostEncServletPage.logout();
|
salesPostEncServletPage.logout();
|
||||||
salesPostEncServletPage.navigateTo();
|
salesPostEncServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -358,10 +367,10 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
public void salesPostPassiveTest() {
|
public void salesPostPassiveTest() {
|
||||||
salesPostPassiveServletPage.navigateTo();
|
salesPostPassiveServletPage.navigateTo();
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body><pre></pre></body>"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
|
||||||
|
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
|
|
||||||
salesPostPassiveServletPage.navigateTo();
|
salesPostPassiveServletPage.navigateTo();
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
@ -369,10 +378,10 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
salesPostPassiveServletPage.logout();
|
salesPostPassiveServletPage.logout();
|
||||||
salesPostPassiveServletPage.navigateTo();
|
salesPostPassiveServletPage.navigateTo();
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body><pre></pre></body>"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
|
||||||
|
|
||||||
salesPostServletPage.navigateTo();
|
salesPostServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
|
||||||
salesPostPassiveServletPage.navigateTo();
|
salesPostPassiveServletPage.navigateTo();
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
|
@ -383,14 +392,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostSigTest() {
|
public void salesPostSigTest() {
|
||||||
salesPostEncServletPage.navigateTo();
|
salesPostEncServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesPostEncServletPage.logout();
|
salesPostEncServletPage.logout();
|
||||||
salesPostEncServletPage.navigateTo();
|
salesPostEncServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -400,14 +409,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostSigEmailTest() {
|
public void salesPostSigEmailTest() {
|
||||||
salesPostSigEmailServletPage.navigateTo();
|
salesPostSigEmailServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
assertTrue(driver.getPageSource().contains("principal=bburke"));
|
||||||
|
|
||||||
salesPostSigEmailServletPage.logout();
|
salesPostSigEmailServletPage.logout();
|
||||||
salesPostSigEmailServletPage.navigateTo();
|
salesPostSigEmailServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -417,15 +426,15 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostSigPersistentTest() {
|
public void salesPostSigPersistentTest() {
|
||||||
salesPostSigPersistentServletPage.navigateTo();
|
salesPostSigPersistentServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertFalse(driver.getPageSource().contains("bburke"));
|
assertFalse(driver.getPageSource().contains("bburke"));
|
||||||
assertTrue(driver.getPageSource().contains("principal=G-"));
|
assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||||
|
|
||||||
salesPostSigPersistentServletPage.logout();
|
salesPostSigPersistentServletPage.logout();
|
||||||
salesPostSigPersistentServletPage.navigateTo();
|
salesPostSigPersistentServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
@ -435,15 +444,15 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
@Test
|
@Test
|
||||||
public void salesPostSigTransientTest() {
|
public void salesPostSigTransientTest() {
|
||||||
salesPostSigTransientServletPage.navigateTo();
|
salesPostSigTransientServletPage.navigateTo();
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
assertFalse(driver.getPageSource().contains("bburke"));
|
assertFalse(driver.getPageSource().contains("bburke"));
|
||||||
assertTrue(driver.getPageSource().contains("principal=G-"));
|
assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||||
|
|
||||||
salesPostSigTransientServletPage.logout();
|
salesPostSigTransientServletPage.logout();
|
||||||
salesPostSigTransientServletPage.navigateTo();
|
salesPostSigTransientServletPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
testRealmSAMLLoginPage.form().login("unauthorized", "password");
|
testRealmSAMLPostLoginPage.form().login("unauthorized", "password");
|
||||||
assertFalse(driver.getPageSource().contains("principal="));
|
assertFalse(driver.getPageSource().contains("principal="));
|
||||||
//Different 403 status page on EAP and Wildfly
|
//Different 403 status page on EAP and Wildfly
|
||||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.authentication;
|
package org.keycloak.testsuite.admin.authentication;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
|
@ -15,7 +15,7 @@
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.event;
|
package org.keycloak.testsuite.admin.event;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
|
@ -15,7 +15,7 @@
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.event;
|
package org.keycloak.testsuite.admin.event;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
|
@ -15,7 +15,7 @@
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.event;
|
package org.keycloak.testsuite.admin.event;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
|
@ -15,7 +15,7 @@
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.event;
|
package org.keycloak.testsuite.admin.event;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
|
@ -0,0 +1,464 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.admin.partialimport;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||||
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.partialimport.PartialImportResult;
|
||||||
|
import org.keycloak.partialimport.PartialImportResults;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractAuthTest;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation.Policy;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RolesRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the partial import endpoint in admin client. Also tests the
|
||||||
|
* server side functionality of each resource along with "fail, skip, overwrite"
|
||||||
|
* functions.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class PartialImportTest extends AbstractAuthTest {
|
||||||
|
|
||||||
|
private static final int NUM_RESOURCE_TYPES = 5;
|
||||||
|
private static final String CLIENT_ROLES_CLIENT = "clientRolesClient";
|
||||||
|
private static final String USER_PREFIX = "user";
|
||||||
|
private static final String CLIENT_PREFIX = "client";
|
||||||
|
private static final String REALM_ROLE_PREFIX = "realmRole";
|
||||||
|
private static final String CLIENT_ROLE_PREFIX = "clientRole";
|
||||||
|
private static final String[] IDP_ALIASES = {"twitter", "github", "facebook", "google", "linkedin", "microsoft", "stackoverflow"};
|
||||||
|
private static final int NUM_ENTITIES = IDP_ALIASES.length;
|
||||||
|
|
||||||
|
private PartialImportRepresentation piRep;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
piRep = new PartialImportRepresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createClientForClientRoles() {
|
||||||
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
|
client.setClientId(CLIENT_ROLES_CLIENT);
|
||||||
|
client.setName(CLIENT_ROLES_CLIENT);
|
||||||
|
client.setRootUrl("foo");
|
||||||
|
client.setProtocol("openid-connect");
|
||||||
|
Response resp = testRealmResource().clients().create(client);
|
||||||
|
|
||||||
|
// for some reason, findAll() will later fail unless readEntity is called here
|
||||||
|
resp.readEntity(String.class);
|
||||||
|
//testRealmResource().clients().findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void removeUsers() {
|
||||||
|
List<UserRepresentation> toRemove = testRealmResource().users().search(USER_PREFIX, 0, NUM_ENTITIES);
|
||||||
|
for (UserRepresentation user : toRemove) {
|
||||||
|
testRealmResource().users().get(user.getId()).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void removeClients() {
|
||||||
|
List<ClientRepresentation> toRemove = testRealmResource().clients().findAll();
|
||||||
|
for (ClientRepresentation client : toRemove) {
|
||||||
|
if (client.getName().startsWith(CLIENT_PREFIX)) {
|
||||||
|
testRealmResource().clients().get(client.getId()).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void removeProviders() {
|
||||||
|
List<IdentityProviderRepresentation> toRemove = testRealmResource().identityProviders().findAll();
|
||||||
|
for (IdentityProviderRepresentation idp : toRemove) {
|
||||||
|
testRealmResource().identityProviders().get(idp.getInternalId()).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void removeRealmRoles() {
|
||||||
|
List<RoleRepresentation> toRemove = testRealmResource().roles().list();
|
||||||
|
for (RoleRepresentation role : toRemove) {
|
||||||
|
if (role.getName().startsWith(REALM_ROLE_PREFIX)) {
|
||||||
|
testRealmResource().roles().get(role.getName()).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void removeClientRoles() {
|
||||||
|
List<RoleRepresentation> toRemove = clientRolesClient().roles().list();
|
||||||
|
for (RoleRepresentation role : toRemove) {
|
||||||
|
if (role.getName().startsWith(CLIENT_ROLE_PREFIX)) {
|
||||||
|
testRealmResource().clients().get(CLIENT_ROLES_CLIENT).roles().get(role.getName()).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientResource clientRolesClient() {
|
||||||
|
return ApiUtil.findClientResourceByName(testRealmResource(), CLIENT_ROLES_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFail() {
|
||||||
|
piRep.setIfResourceExists(Policy.FAIL.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSkip() {
|
||||||
|
piRep.setIfResourceExists(Policy.SKIP.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOverwrite() {
|
||||||
|
piRep.setIfResourceExists(Policy.OVERWRITE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartialImportResults doImport() {
|
||||||
|
Response response = testRealmResource().partialImport(piRep);
|
||||||
|
return response.readEntity(PartialImportResults.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUsers() {
|
||||||
|
List<UserRepresentation> users = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_ENTITIES; i++) {
|
||||||
|
UserRepresentation user = createUserRepresentation(USER_PREFIX + i, USER_PREFIX + i + "@foo.com", "foo", "bar", true);
|
||||||
|
users.add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
piRep.setUsers(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addClients() {
|
||||||
|
List<ClientRepresentation> clients = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_ENTITIES; i++) {
|
||||||
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
|
client.setClientId(CLIENT_PREFIX + i);
|
||||||
|
client.setName(CLIENT_PREFIX + i);
|
||||||
|
client.setRootUrl("foo");
|
||||||
|
clients.add(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
piRep.setClients(clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProviders() {
|
||||||
|
List<IdentityProviderRepresentation> providers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String alias : IDP_ALIASES) {
|
||||||
|
IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation();
|
||||||
|
idpRep.setAlias(alias);
|
||||||
|
idpRep.setProviderId(alias);
|
||||||
|
idpRep.setEnabled(true);
|
||||||
|
idpRep.setAuthenticateByDefault(false);
|
||||||
|
idpRep.setFirstBrokerLoginFlowAlias("first broker login");
|
||||||
|
|
||||||
|
Map<String, String> config = new HashMap<>();
|
||||||
|
config.put("clientSecret", "secret");
|
||||||
|
config.put("clientId", alias);
|
||||||
|
idpRep.setConfig(config);
|
||||||
|
providers.add(idpRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
piRep.setIdentityProviders(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RoleRepresentation> makeRoles(String prefix) {
|
||||||
|
List<RoleRepresentation> roles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_ENTITIES; i++) {
|
||||||
|
RoleRepresentation role = new RoleRepresentation();
|
||||||
|
role.setName(prefix + i);
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRealmRoles() {
|
||||||
|
RolesRepresentation roles = piRep.getRoles();
|
||||||
|
if (roles == null) roles = new RolesRepresentation();
|
||||||
|
roles.setRealm(makeRoles(REALM_ROLE_PREFIX));
|
||||||
|
piRep.setRoles(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addClientRoles() {
|
||||||
|
RolesRepresentation roles = piRep.getRoles();
|
||||||
|
if (roles == null) roles = new RolesRepresentation();
|
||||||
|
Map<String, List<RoleRepresentation>> clientRolesMap = new HashMap<>();
|
||||||
|
clientRolesMap.put(CLIENT_ROLES_CLIENT, makeRoles(CLIENT_ROLE_PREFIX));
|
||||||
|
roles.setClient(clientRolesMap);
|
||||||
|
piRep.setRoles(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddUsers() {
|
||||||
|
setFail();
|
||||||
|
addUsers();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
for (PartialImportResult result : results.getResults()) {
|
||||||
|
String id = result.getId();
|
||||||
|
UserResource userRsc = testRealmResource().users().get(id);
|
||||||
|
UserRepresentation user = userRsc.toRepresentation();
|
||||||
|
assertTrue(user.getUsername().startsWith(USER_PREFIX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClients() {
|
||||||
|
setFail();
|
||||||
|
addClients();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
for (PartialImportResult result : results.getResults()) {
|
||||||
|
String id = result.getId();
|
||||||
|
ClientResource clientRsc = testRealmResource().clients().get(id);
|
||||||
|
ClientRepresentation client = clientRsc.toRepresentation();
|
||||||
|
assertTrue(client.getName().startsWith(CLIENT_PREFIX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddProviders() {
|
||||||
|
setFail();
|
||||||
|
addProviders();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(IDP_ALIASES.length, results.getAdded());
|
||||||
|
|
||||||
|
for (PartialImportResult result : results.getResults()) {
|
||||||
|
String id = result.getId();
|
||||||
|
IdentityProviderResource idpRsc = testRealmResource().identityProviders().get(id);
|
||||||
|
IdentityProviderRepresentation idp = idpRsc.toRepresentation();
|
||||||
|
Map<String, String> config = idp.getConfig();
|
||||||
|
assertTrue(Arrays.asList(IDP_ALIASES).contains(config.get("clientId")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRealmRoles() {
|
||||||
|
setFail();
|
||||||
|
addRealmRoles();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
for (PartialImportResult result : results.getResults()) {
|
||||||
|
String name = result.getResourceName();
|
||||||
|
RoleResource roleRsc = testRealmResource().roles().get(name);
|
||||||
|
RoleRepresentation role = roleRsc.toRepresentation();
|
||||||
|
assertTrue(role.getName().startsWith(REALM_ROLE_PREFIX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientRoles() {
|
||||||
|
setFail();
|
||||||
|
addClientRoles();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
List<RoleRepresentation> clientRoles = clientRolesClient().roles().list();
|
||||||
|
assertEquals(NUM_ENTITIES, clientRoles.size());
|
||||||
|
|
||||||
|
for (RoleRepresentation roleRep : clientRoles) {
|
||||||
|
assertTrue(roleRep.getName().startsWith(CLIENT_ROLE_PREFIX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testFail() {
|
||||||
|
setFail();
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertNull(results.getErrorMessage());
|
||||||
|
results = doImport(); // second time should fail
|
||||||
|
assertNotNull(results.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddUsersFail() {
|
||||||
|
addUsers();
|
||||||
|
testFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientsFail() {
|
||||||
|
addClients();
|
||||||
|
testFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddProvidersFail() {
|
||||||
|
addProviders();
|
||||||
|
testFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRealmRolesFail() {
|
||||||
|
addRealmRoles();
|
||||||
|
testFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientRolesFail() {
|
||||||
|
addClientRoles();
|
||||||
|
testFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testSkip() {
|
||||||
|
setSkip();
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getSkipped());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddUsersSkip() {
|
||||||
|
addUsers();
|
||||||
|
testSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientsSkip() {
|
||||||
|
addClients();
|
||||||
|
testSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddProvidersSkip() {
|
||||||
|
addProviders();
|
||||||
|
testSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRealmRolesSkip() {
|
||||||
|
addRealmRoles();
|
||||||
|
testSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientRolesSkip() {
|
||||||
|
addClientRoles();
|
||||||
|
testSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testOverwrite() {
|
||||||
|
setOverwrite();
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getAdded());
|
||||||
|
|
||||||
|
results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES, results.getOverwritten());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddUsersOverwrite() {
|
||||||
|
addUsers();
|
||||||
|
testOverwrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientsOverwrite() {
|
||||||
|
addClients();
|
||||||
|
testOverwrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddProvidersOverwrite() {
|
||||||
|
addProviders();
|
||||||
|
testOverwrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRealmRolesOverwrite() {
|
||||||
|
addRealmRoles();
|
||||||
|
testOverwrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddClientRolesOverwrite() {
|
||||||
|
addClientRoles();
|
||||||
|
testOverwrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void importEverything() {
|
||||||
|
addUsers();
|
||||||
|
addClients();
|
||||||
|
addProviders();
|
||||||
|
addRealmRoles();
|
||||||
|
addClientRoles();
|
||||||
|
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertNull(results.getErrorMessage());
|
||||||
|
assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getAdded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEverythingFail() {
|
||||||
|
setFail();
|
||||||
|
importEverything();
|
||||||
|
PartialImportResults results = doImport(); // second import will fail because not allowed to skip or overwrite
|
||||||
|
assertNotNull(results.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEverythingSkip() {
|
||||||
|
setSkip();
|
||||||
|
importEverything();
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getSkipped());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEverythingOverwrite() {
|
||||||
|
setOverwrite();
|
||||||
|
importEverything();
|
||||||
|
PartialImportResults results = doImport();
|
||||||
|
assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getOverwritten());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,22 @@
|
||||||
package org.keycloak.testsuite.cluster;
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import org.jboss.arquillian.container.test.api.ContainerController;
|
import org.jboss.arquillian.container.test.api.ContainerController;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.junit.Before;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
|
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
|
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -24,52 +27,103 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
|
||||||
@ArquillianResource
|
@ArquillianResource
|
||||||
protected ContainerController controller;
|
protected ContainerController controller;
|
||||||
|
|
||||||
protected List<Keycloak> backendAdminClients = new ArrayList<>();
|
protected Map<ContainerInfo, Keycloak> backendAdminClients = new HashMap<>();
|
||||||
|
|
||||||
public void startBackendNodes(int count) {
|
private int currentFailNodeIndex = 0;
|
||||||
if (count < 0 || count > 10) {
|
|
||||||
throw new IllegalArgumentException();
|
public int getClusterSize() {
|
||||||
|
return suiteContext.getAuthServerBackendsInfo().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void iterateCurrentFailNode() {
|
||||||
|
currentFailNodeIndex++;
|
||||||
|
if (currentFailNodeIndex >= getClusterSize()) {
|
||||||
|
currentFailNodeIndex = 0;
|
||||||
}
|
}
|
||||||
assertTrue(suiteContext.getAuthServerBackendsInfo().size() >= count);
|
logFailoverSetup();
|
||||||
for (int i = 0; i < count; i++) {
|
}
|
||||||
|
|
||||||
ContainerInfo backendNode = suiteContext.getAuthServerBackendsInfo().get(i);
|
protected ContainerInfo getCurrentFailNode() {
|
||||||
|
return backendNode(currentFailNodeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
controller.start(backendNode.getQualifier());
|
protected Set<ContainerInfo> getCurrentSurvivorNodes() {
|
||||||
assertTrue(controller.isStarted(backendNode.getQualifier()));
|
Set<ContainerInfo> survivors = new HashSet<>(suiteContext.getAuthServerBackendsInfo());
|
||||||
|
survivors.remove(getCurrentFailNode());
|
||||||
|
return survivors;
|
||||||
|
}
|
||||||
|
|
||||||
backendAdminClients.add(createAdminClientFor(backendNode));
|
protected void logFailoverSetup() {
|
||||||
|
log.info("Current failover setup");
|
||||||
|
boolean started = controller.isStarted(getCurrentFailNode().getQualifier());
|
||||||
|
log.info("Fail node: " + getCurrentFailNode() + (started ? "" : " (stopped)"));
|
||||||
|
for (ContainerInfo survivor : getCurrentSurvivorNodes()) {
|
||||||
|
started = controller.isStarted(survivor.getQualifier());
|
||||||
|
log.info("Survivor: " + survivor + (started ? "" : " (stopped)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Keycloak createAdminClientFor(ContainerInfo backendNode) {
|
public void failure() {
|
||||||
log.info("Initializing admin client for " + backendNode.getContextRoot() + "/auth");
|
log.info("Simulating failure");
|
||||||
return Keycloak.getInstance(backendNode.getContextRoot() + "/auth",
|
killBackendNode(getCurrentFailNode());
|
||||||
MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
|
}
|
||||||
|
|
||||||
|
public void failback() {
|
||||||
|
log.info("Bringing all backend nodes online");
|
||||||
|
for (ContainerInfo node : suiteContext.getAuthServerBackendsInfo()) {
|
||||||
|
startBackendNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContainerInfo frontendNode() {
|
||||||
|
return suiteContext.getAuthServerInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ContainerInfo backendNode(int i) {
|
protected ContainerInfo backendNode(int i) {
|
||||||
return suiteContext.getAuthServerBackendsInfo().get(i);
|
return suiteContext.getAuthServerBackendsInfo().get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startBackendNode(int i) {
|
protected void startBackendNode(ContainerInfo node) {
|
||||||
String container = backendNode(i).getQualifier();
|
if (!controller.isStarted(node.getQualifier())) {
|
||||||
if (!controller.isStarted(container)) {
|
log.info("Starting backend node: " + node);
|
||||||
controller.start(container);
|
controller.start(node.getQualifier());
|
||||||
backendAdminClients.set(i, createAdminClientFor(backendNode(i)));
|
assertTrue(controller.isStarted(node.getQualifier()));
|
||||||
|
}
|
||||||
|
log.info("Backend node " + node + " is started");
|
||||||
|
if (!backendAdminClients.containsKey(node)) {
|
||||||
|
backendAdminClients.put(node, createAdminClientFor(node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void killBackendNode(int i) {
|
protected Keycloak createAdminClientFor(ContainerInfo node) {
|
||||||
backendAdminClients.get(i).close();
|
log.info("Initializing admin client for " + node.getContextRoot() + "/auth");
|
||||||
controller.kill(backendNode(i).getQualifier());
|
return Keycloak.getInstance(node.getContextRoot() + "/auth",
|
||||||
|
MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void listRealms(int i) {
|
protected void killBackendNode(ContainerInfo node) {
|
||||||
log.info(String.format("Node %s: AccessTokenString: %s", i + 1, backendAdminClients.get(i).tokenManager().getAccessTokenString()));
|
backendAdminClients.get(node).close();
|
||||||
for (RealmRepresentation r : backendAdminClients.get(i).realms().findAll()) {
|
backendAdminClients.remove(node);
|
||||||
log.info(String.format("Node %s: Realm: %s, Id: %s", i + 1, r.getRealm(), r.getId()));
|
log.info("Killing backend node: " + node);
|
||||||
}
|
controller.kill(node.getQualifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Keycloak getAdminClientFor(ContainerInfo node) {
|
||||||
|
return node.equals(suiteContext.getAuthServerInfo())
|
||||||
|
? adminClient // frontend client
|
||||||
|
: backendAdminClients.get(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeClusterTest() {
|
||||||
|
failback();
|
||||||
|
logFailoverSetup();
|
||||||
|
pause(3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
// no test realms will be created by the default
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||||
|
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
|
||||||
|
import org.apache.commons.lang.builder.ToStringStyle;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
* @param <T> entity representation
|
||||||
|
* @param <TR> entity resource
|
||||||
|
*/
|
||||||
|
public abstract class AbstractInvalidationClusterTest<T, TR> extends AbstractClusterTest {
|
||||||
|
|
||||||
|
protected RealmRepresentation createTestRealmRepresentation() {
|
||||||
|
RealmRepresentation testRealm = new RealmRepresentation();
|
||||||
|
testRealm.setRealm("test_" + RandomStringUtils.randomAlphabetic(5));
|
||||||
|
testRealm.setEnabled(true);
|
||||||
|
return testRealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T createTestEntityRepresentation();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void crudWithoutFailover() {
|
||||||
|
crud(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void crudWithFailover() {
|
||||||
|
crud(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void crud(boolean backendFailover) {
|
||||||
|
T testEntity = createTestEntityRepresentation();
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
testEntity = createEntityOnCurrentFailNode(testEntity);
|
||||||
|
|
||||||
|
if (backendFailover) {
|
||||||
|
failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEntityOnSurvivorNodesEqualsTo(testEntity);
|
||||||
|
|
||||||
|
failback();
|
||||||
|
iterateCurrentFailNode();
|
||||||
|
|
||||||
|
// UPDATE(s)
|
||||||
|
testEntity = testEntityUpdates(testEntity, backendFailover);
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
deleteEntityOnCurrentFailNode(testEntity);
|
||||||
|
|
||||||
|
if (backendFailover) {
|
||||||
|
failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEntityOnSurvivorNodesIsDeleted(testEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract TR entityResource(T testEntity, ContainerInfo node);
|
||||||
|
|
||||||
|
protected abstract TR entityResource(String idOrName, ContainerInfo node);
|
||||||
|
|
||||||
|
protected abstract T createEntity(T testEntity, ContainerInfo node);
|
||||||
|
|
||||||
|
protected abstract T readEntity(T entity, ContainerInfo node);
|
||||||
|
|
||||||
|
protected abstract T updateEntity(T entity, ContainerInfo node);
|
||||||
|
|
||||||
|
protected abstract void deleteEntity(T testEntity, ContainerInfo node);
|
||||||
|
|
||||||
|
protected TR entityResourceOnCurrentFailNode(T testEntity) {
|
||||||
|
return entityResource(testEntity, getCurrentFailNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getEntityType(T entity) {
|
||||||
|
return entity.getClass().getSimpleName().replace("Representation", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T createEntityOnCurrentFailNode(T entity) {
|
||||||
|
log.info("Creating " + getEntityType(entity) + " on " + getCurrentFailNode());
|
||||||
|
return createEntity(entity, getCurrentFailNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T readEntityOnCurrentFailNode(T entity) {
|
||||||
|
log.debug("Reading " + getEntityType(entity) + " on " + getCurrentFailNode());
|
||||||
|
return readEntity(entity, getCurrentFailNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T updateEntityOnCurrentFailNode(T entity) {
|
||||||
|
return updateEntityOnCurrentFailNode(entity, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T updateEntityOnCurrentFailNode(T entity, String updateType) {
|
||||||
|
log.info("Updating " + getEntityType(entity) + " " + updateType + " on " + getCurrentFailNode());
|
||||||
|
return updateEntity(entity, getCurrentFailNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void deleteEntityOnCurrentFailNode(T entity) {
|
||||||
|
log.info("Creating " + getEntityType(entity) + " on " + getCurrentFailNode());
|
||||||
|
deleteEntity(entity, getCurrentFailNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T testEntityUpdates(T testEntity, boolean backendFailover);
|
||||||
|
|
||||||
|
protected void verifyEntityUpdateDuringFailover(T testEntity, boolean backendFailover) {
|
||||||
|
if (backendFailover) {
|
||||||
|
failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEntityOnSurvivorNodesEqualsTo(testEntity);
|
||||||
|
|
||||||
|
failback();
|
||||||
|
iterateCurrentFailNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> excludedComparisonFields = new ArrayList<>();
|
||||||
|
|
||||||
|
protected void assertEntityOnSurvivorNodesEqualsTo(T testEntityOnFailNode) {
|
||||||
|
boolean entityDiffers = false;
|
||||||
|
for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
|
||||||
|
T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
|
||||||
|
if (EqualsBuilder.reflectionEquals(testEntityOnSurvivorNode, testEntityOnFailNode, excludedComparisonFields)) {
|
||||||
|
log.info(String.format("Verification of %s on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
|
||||||
|
} else {
|
||||||
|
entityDiffers = true;
|
||||||
|
log.error(String.format("Verification of %s on survivor %s FAILED", getEntityType(testEntityOnFailNode), survivorNode));
|
||||||
|
String tf = ReflectionToStringBuilder.reflectionToString(testEntityOnFailNode, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
|
String ts = ReflectionToStringBuilder.reflectionToString(testEntityOnSurvivorNode, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
|
log.error(String.format(
|
||||||
|
"\nEntity on fail node: \n%s\n"
|
||||||
|
+ "\nEntity on survivor node: \n%s\n"
|
||||||
|
+ "\nDifference: \n%s\n",
|
||||||
|
tf, ts, StringUtils.difference(tf, ts)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(entityDiffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEntityOnSurvivorNodesIsDeleted(T testEntityOnFailNode) {
|
||||||
|
// check if deleted from all survivor nodes
|
||||||
|
boolean entityExists = false;
|
||||||
|
for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
|
||||||
|
T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
|
||||||
|
if (testEntityOnSurvivorNode == null) {
|
||||||
|
log.info(String.format("Verification of %s deletion on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
|
||||||
|
} else {
|
||||||
|
entityExists = true;
|
||||||
|
log.error(String.format("Verification of %s deletion on survivor %s FAILED", getEntityType(testEntityOnFailNode), survivorNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(entityExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public abstract class AbstractInvalidationClusterTestWithTestRealm<T, TR> extends AbstractInvalidationClusterTest<T, TR> {
|
||||||
|
|
||||||
|
protected String testRealmName = null;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createTestRealm() {
|
||||||
|
createTestRealm(frontendNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createTestRealm(ContainerInfo node) {
|
||||||
|
RealmRepresentation r = createTestRealmRepresentation();
|
||||||
|
getAdminClientFor(node).realms().create(r);
|
||||||
|
testRealmName = r.getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,57 +0,0 @@
|
||||||
package org.keycloak.testsuite.cluster;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.keycloak.admin.client.Keycloak;
|
|
||||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public abstract class AbstractTwoNodeClusterTest extends AbstractClusterTest {
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeTwoNodeClusterTest() {
|
|
||||||
startBackendNodes(2);
|
|
||||||
pause(3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ContainerInfo backend1Info() {
|
|
||||||
return backendNode(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ContainerInfo backend2Info() {
|
|
||||||
return backendNode(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Keycloak backend1AdminClient() {
|
|
||||||
return backendAdminClients.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Keycloak backend2AdminClient() {
|
|
||||||
return backendAdminClients.get(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void startBackend1() {
|
|
||||||
startBackendNode(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void startBackend2() {
|
|
||||||
startBackendNode(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void failback() {
|
|
||||||
startBackend1();
|
|
||||||
startBackend2();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void killBackend1() {
|
|
||||||
killBackendNode(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void killBackend2() {
|
|
||||||
killBackendNode(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public class ClientInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<ClientRepresentation, ClientResource> {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setExcludedComparisonFields() {
|
||||||
|
excludedComparisonFields.add("protocolMappers");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientRepresentation createTestEntityRepresentation() {
|
||||||
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
|
String s = RandomStringUtils.randomAlphabetic(5);
|
||||||
|
client.setClientId("client_" + s);
|
||||||
|
client.setName("name_" + s);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ClientsResource clients(ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realm(testRealmName).clients();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientResource entityResource(ClientRepresentation client, ContainerInfo node) {
|
||||||
|
return entityResource(client.getId(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientResource entityResource(String id, ContainerInfo node) {
|
||||||
|
return clients(node).get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientRepresentation createEntity(ClientRepresentation client, ContainerInfo node) {
|
||||||
|
Response response = clients(node).create(client);
|
||||||
|
String id = ApiUtil.getCreatedId(response);
|
||||||
|
response.close();
|
||||||
|
client.setId(id);
|
||||||
|
return readEntity(client, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientRepresentation readEntity(ClientRepresentation client, ContainerInfo node) {
|
||||||
|
ClientRepresentation u = null;
|
||||||
|
try {
|
||||||
|
u = entityResource(client, node).toRepresentation();
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// expected when client doesn't exist
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientRepresentation updateEntity(ClientRepresentation client, ContainerInfo node) {
|
||||||
|
entityResource(client, node).update(client);
|
||||||
|
return readEntity(client, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteEntity(ClientRepresentation client, ContainerInfo node) {
|
||||||
|
entityResource(client, node).remove();
|
||||||
|
assertNull(readEntity(client, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClientRepresentation testEntityUpdates(ClientRepresentation client, boolean backendFailover) {
|
||||||
|
|
||||||
|
// clientId
|
||||||
|
client.setClientId(client.getClientId() + "_updated");
|
||||||
|
client = updateEntityOnCurrentFailNode(client, "clientId");
|
||||||
|
verifyEntityUpdateDuringFailover(client, backendFailover);
|
||||||
|
|
||||||
|
// name
|
||||||
|
client.setName(client.getName() + "_updated");
|
||||||
|
client = updateEntityOnCurrentFailNode(client, "name");
|
||||||
|
verifyEntityUpdateDuringFailover(client, backendFailover);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
package org.keycloak.testsuite.cluster;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class EntityInvalidationClusterTest extends AbstractTwoNodeClusterTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void realmCRUDWithoutFailover() {
|
|
||||||
realmCRUD(TEST + "_wofo", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void realmCRUDWithFailover() {
|
|
||||||
realmCRUD(TEST + "_wfo", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void realmCRUD(String realm, boolean containerFailover) {
|
|
||||||
RealmRepresentation testRealm = new RealmRepresentation();
|
|
||||||
testRealm.setRealm(realm);
|
|
||||||
testRealm.setEnabled(true);
|
|
||||||
|
|
||||||
// CREATE on node1
|
|
||||||
backend1AdminClient().realms().create(testRealm);
|
|
||||||
|
|
||||||
// check if created on node1
|
|
||||||
RealmRepresentation testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
|
|
||||||
assertEquals(testRealmOnBackend1.getRealm(), testRealm.getRealm());
|
|
||||||
if (containerFailover) {
|
|
||||||
killBackend1();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if created on node2
|
|
||||||
RealmRepresentation testRealmOnBackend2 = backend2AdminClient().realms().realm(realm).toRepresentation();
|
|
||||||
assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
|
|
||||||
assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
|
|
||||||
|
|
||||||
failback();
|
|
||||||
|
|
||||||
// UPDATE on node2
|
|
||||||
String realmUpdated = realm + "_updated";
|
|
||||||
testRealmOnBackend2.setRealm(realmUpdated);
|
|
||||||
backend2AdminClient().realms().realm(realm).update(testRealmOnBackend2);
|
|
||||||
if (containerFailover) {
|
|
||||||
killBackend2();
|
|
||||||
}
|
|
||||||
// check if updated on node1
|
|
||||||
testRealmOnBackend1 = backend1AdminClient().realms().realm(realmUpdated).toRepresentation();
|
|
||||||
assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
|
|
||||||
assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
|
|
||||||
|
|
||||||
failback();
|
|
||||||
|
|
||||||
// DELETE on node1
|
|
||||||
backend1AdminClient().realms().realm(realmUpdated).remove();
|
|
||||||
if (containerFailover) {
|
|
||||||
killBackend1();
|
|
||||||
}
|
|
||||||
// check if deleted on node2
|
|
||||||
boolean testRealmOnBackend2Exists = false;
|
|
||||||
for (RealmRepresentation realmOnBackend2 : backend2AdminClient().realms().findAll()) {
|
|
||||||
if (realmUpdated.equals(realmOnBackend2.getRealm())
|
|
||||||
|| testRealmOnBackend1.getId().equals(realmOnBackend2.getId())) {
|
|
||||||
testRealmOnBackend2Exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertFalse(testRealmOnBackend2Exists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createRealmViaFrontend() {
|
|
||||||
String realm = TEST + "_fe";
|
|
||||||
|
|
||||||
RealmRepresentation testRealm = new RealmRepresentation();
|
|
||||||
testRealm.setRealm(realm);
|
|
||||||
testRealm.setEnabled(true);
|
|
||||||
|
|
||||||
// CREATE on frontend
|
|
||||||
adminClient.realms().create(testRealm);
|
|
||||||
|
|
||||||
// check if created on frontend
|
|
||||||
RealmRepresentation testRealmOnFrontend = adminClient.realms().realm(realm).toRepresentation();
|
|
||||||
assertEquals(testRealmOnFrontend.getRealm(), testRealm.getRealm());
|
|
||||||
|
|
||||||
// check if created on node1
|
|
||||||
RealmRepresentation testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
|
|
||||||
assertEquals(testRealmOnBackend1.getId(), testRealmOnFrontend.getId());
|
|
||||||
assertEquals(testRealmOnBackend1.getRealm(), testRealmOnFrontend.getRealm());
|
|
||||||
|
|
||||||
// check if created on node2
|
|
||||||
RealmRepresentation testRealmOnBackend2 = backend2AdminClient().realms().realm(realm).toRepresentation();
|
|
||||||
assertEquals(testRealmOnBackend2.getId(), testRealmOnFrontend.getId());
|
|
||||||
assertEquals(testRealmOnBackend2.getRealm(), testRealmOnFrontend.getRealm());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.admin.client.resource.GroupResource;
|
||||||
|
import org.keycloak.admin.client.resource.GroupsResource;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public class GroupInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<GroupRepresentation, GroupResource> {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setExcludedComparisonFields() {
|
||||||
|
excludedComparisonFields.add("subGroups");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupRepresentation createTestEntityRepresentation() {
|
||||||
|
GroupRepresentation group = new GroupRepresentation();
|
||||||
|
group.setName("group_" + RandomStringUtils.randomAlphabetic(5));
|
||||||
|
group.setAttributes(new HashMap<String, List<String>>());
|
||||||
|
group.getAttributes().put("attr1", Arrays.asList(new String[]{"attr1 value"}));
|
||||||
|
group.getAttributes().put("attr2", Arrays.asList(new String[]{"attr2 value", "attr2 value2"}));
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GroupsResource groups(ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realm(testRealmName).groups();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupResource entityResource(GroupRepresentation group, ContainerInfo node) {
|
||||||
|
return entityResource(group.getId(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupResource entityResource(String id, ContainerInfo node) {
|
||||||
|
return groups(node).group(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupRepresentation createEntity(GroupRepresentation group, ContainerInfo node) {
|
||||||
|
Response response = groups(node).add(group);
|
||||||
|
String id = ApiUtil.getCreatedId(response);
|
||||||
|
response.close();
|
||||||
|
group.setId(id);
|
||||||
|
return readEntity(group, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupRepresentation readEntity(GroupRepresentation group, ContainerInfo node) {
|
||||||
|
GroupRepresentation u = null;
|
||||||
|
try {
|
||||||
|
u = entityResource(group, node).toRepresentation();
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// expected when group doesn't exist
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupRepresentation updateEntity(GroupRepresentation group, ContainerInfo node) {
|
||||||
|
entityResource(group, node).update(group);
|
||||||
|
return readEntity(group, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteEntity(GroupRepresentation group, ContainerInfo node) {
|
||||||
|
entityResource(group, node).remove();
|
||||||
|
assertNull(readEntity(group, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GroupRepresentation testEntityUpdates(GroupRepresentation group, boolean backendFailover) {
|
||||||
|
|
||||||
|
// groupname
|
||||||
|
group.setName(group.getName() + "_updated");
|
||||||
|
group = updateEntityOnCurrentFailNode(group, "name");
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
// attributes - add new
|
||||||
|
group.getAttributes().put("attr3", Arrays.asList(new String[]{"attr3 value"}));
|
||||||
|
group = updateEntityOnCurrentFailNode(group, "attributes - adding");
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
// attributes - remove
|
||||||
|
group.getAttributes().remove("attr3");
|
||||||
|
group = updateEntityOnCurrentFailNode(group, "attributes - removing");
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
// attributes - update 1
|
||||||
|
group.getAttributes().get("attr1").set(0,
|
||||||
|
group.getAttributes().get("attr1").get(0) + " - updated");
|
||||||
|
group = updateEntityOnCurrentFailNode(group, "attributes");
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
// attributes - update 2
|
||||||
|
group.getAttributes().get("attr2").set(1,
|
||||||
|
group.getAttributes().get("attr2").get(1) + " - updated");
|
||||||
|
group = updateEntityOnCurrentFailNode(group, "attributes");
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
// move
|
||||||
|
log.info("Updating Group parent on " + getCurrentFailNode());
|
||||||
|
GroupRepresentation parentGroup = new GroupRepresentation();
|
||||||
|
parentGroup.setName("parent");
|
||||||
|
parentGroup = createEntityOnCurrentFailNode(parentGroup);
|
||||||
|
assertEquals("/" + parentGroup.getName(), parentGroup.getPath());
|
||||||
|
|
||||||
|
Response r = entityResourceOnCurrentFailNode(parentGroup).subGroup(group);
|
||||||
|
r.close();
|
||||||
|
parentGroup = readEntityOnCurrentFailNode(parentGroup);
|
||||||
|
group = readEntityOnCurrentFailNode(group);
|
||||||
|
|
||||||
|
assertTrue(ApiUtil.groupContainsSubgroup(parentGroup, group));
|
||||||
|
assertEquals(parentGroup.getPath() + "/" + group.getName(), group.getPath());
|
||||||
|
|
||||||
|
verifyEntityUpdateDuringFailover(group, backendFailover);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.admin.client.resource.RealmsResource;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public class RealmInvalidationClusterTest extends AbstractInvalidationClusterTest<RealmRepresentation, RealmResource> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmRepresentation createTestEntityRepresentation() {
|
||||||
|
return createTestRealmRepresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RealmsResource realms(ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmResource entityResource(RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
return entityResource(realm.getRealm(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmResource entityResource(String name, ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realm(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmRepresentation createEntity(RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
realms(node).create(realm);
|
||||||
|
return readEntity(realm, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmRepresentation readEntity(RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
RealmRepresentation realmOnNode = null;
|
||||||
|
try {
|
||||||
|
realmOnNode = entityResource(realm, node).toRepresentation();
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// expected if realm not found
|
||||||
|
}
|
||||||
|
return realmOnNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmRepresentation updateEntity(RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
return updateEntity(realm.getRealm(), realm, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RealmRepresentation updateEntity(String realmName, RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
entityResource(realmName, node).update(realm);
|
||||||
|
return readEntity(realm, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteEntity(RealmRepresentation realm, ContainerInfo node) {
|
||||||
|
entityResource(realm, node).remove();
|
||||||
|
// check if deleted
|
||||||
|
assertNull(readEntity(realm, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmRepresentation testEntityUpdates(RealmRepresentation realm, boolean backendFailover) {
|
||||||
|
|
||||||
|
// realm name
|
||||||
|
String originalName = realm.getRealm();
|
||||||
|
realm.setRealm(realm.getRealm() + "_updated");
|
||||||
|
realm = updateEntity(originalName, realm, getCurrentFailNode());
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
// enabled
|
||||||
|
realm.setEnabled(!realm.isEnabled());
|
||||||
|
realm = updateEntityOnCurrentFailNode(realm, "enabled");
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
// public key
|
||||||
|
realm.setPublicKey("GENERATE");
|
||||||
|
realm = updateEntityOnCurrentFailNode(realm, "public key");
|
||||||
|
assertNotEquals("GENERATE", realm.getPublicKey());
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
// require ssl
|
||||||
|
realm.setSslRequired("all");
|
||||||
|
realm = updateEntityOnCurrentFailNode(realm, "require ssl");
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
// brute force detection
|
||||||
|
realm.setBruteForceProtected(!realm.isBruteForceProtected());
|
||||||
|
realm = updateEntityOnCurrentFailNode(realm, "brute force");
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
// brute force detection - failure factor
|
||||||
|
realm.setBruteForceProtected(true);
|
||||||
|
realm.setFailureFactor(realm.getFailureFactor() + 1);
|
||||||
|
realm = updateEntityOnCurrentFailNode(realm, "brute force failure factor");
|
||||||
|
verifyEntityUpdateDuringFailover(realm, backendFailover);
|
||||||
|
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
|
import org.keycloak.admin.client.resource.RolesResource;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public class RoleInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<RoleRepresentation, RoleResource> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleRepresentation createTestEntityRepresentation() {
|
||||||
|
RoleRepresentation role = new RoleRepresentation();
|
||||||
|
role.setName("role_" + RandomStringUtils.randomAlphabetic(5));
|
||||||
|
role.setComposite(false);
|
||||||
|
role.setDescription("description of "+role.getName());
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RolesResource roles(ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realm(testRealmName).roles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleResource entityResource(RoleRepresentation role, ContainerInfo node) {
|
||||||
|
return entityResource(role.getName(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleResource entityResource(String name, ContainerInfo node) {
|
||||||
|
return roles(node).get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleRepresentation createEntity(RoleRepresentation role, ContainerInfo node) {
|
||||||
|
roles(node).create(role);
|
||||||
|
return readEntity(role, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleRepresentation readEntity(RoleRepresentation role, ContainerInfo node) {
|
||||||
|
RoleRepresentation u = null;
|
||||||
|
try {
|
||||||
|
u = entityResource(role, node).toRepresentation();
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// expected when role doesn't exist
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleRepresentation updateEntity(RoleRepresentation role, ContainerInfo node) {
|
||||||
|
return updateEntity(role.getName(), role, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoleRepresentation updateEntity(String roleName, RoleRepresentation role, ContainerInfo node) {
|
||||||
|
entityResource(roleName, node).update(role);
|
||||||
|
return readEntity(role, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteEntity(RoleRepresentation role, ContainerInfo node) {
|
||||||
|
entityResource(role, node).remove();
|
||||||
|
assertNull(readEntity(role, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RoleRepresentation testEntityUpdates(RoleRepresentation role, boolean backendFailover) {
|
||||||
|
|
||||||
|
// description
|
||||||
|
role.setDescription(role.getDescription()+"_- updated");
|
||||||
|
role = updateEntityOnCurrentFailNode(role, "description");
|
||||||
|
verifyEntityUpdateDuringFailover(role, backendFailover);
|
||||||
|
|
||||||
|
// TODO composites
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import org.openqa.selenium.Cookie;
|
||||||
*
|
*
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
|
public class SessionFailoverClusterTest extends AbstractClusterTest {
|
||||||
|
|
||||||
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
|
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
|
||||||
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||||
|
@ -30,7 +30,7 @@ public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
|
||||||
@Ignore("work in progress") // only works with owners="2" at the moment
|
@Ignore("work in progress") // only works with owners="2" at the moment
|
||||||
public void sessionFailover() {
|
public void sessionFailover() {
|
||||||
|
|
||||||
// LOGOUT
|
// LOGIN
|
||||||
accountPage.navigateTo();
|
accountPage.navigateTo();
|
||||||
driver.navigate().refresh();
|
driver.navigate().refresh();
|
||||||
pause(3000);
|
pause(3000);
|
||||||
|
@ -40,7 +40,7 @@ public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
|
||||||
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||||
assertNotNull(sessionCookie);
|
assertNotNull(sessionCookie);
|
||||||
|
|
||||||
killBackend1();
|
failure();
|
||||||
|
|
||||||
// check if session survived backend failure
|
// check if session survived backend failure
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
|
||||||
assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
|
assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
|
||||||
|
|
||||||
failback();
|
failback();
|
||||||
|
iterateCurrentFailNode();
|
||||||
|
|
||||||
// check if session survived backend failback
|
// check if session survived backend failback
|
||||||
driver.navigate().refresh();
|
driver.navigate().refresh();
|
||||||
|
@ -71,7 +72,7 @@ public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
|
||||||
sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||||
assertNull(sessionCookie);
|
assertNull(sessionCookie);
|
||||||
|
|
||||||
killBackend1();
|
failure();
|
||||||
|
|
||||||
// check if session survived backend failure
|
// check if session survived backend failure
|
||||||
driver.navigate().refresh();
|
driver.navigate().refresh();
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.keycloak.testsuite.cluster;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.admin.client.resource.UsersResource;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author tkyjovsk
|
||||||
|
*/
|
||||||
|
public class UserInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<UserRepresentation, UserResource> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserRepresentation createTestEntityRepresentation() {
|
||||||
|
String firstName = "user";
|
||||||
|
String lastName = RandomStringUtils.randomAlphabetic(5);
|
||||||
|
UserRepresentation user = new UserRepresentation();
|
||||||
|
user.setUsername(firstName + "_" + lastName);
|
||||||
|
user.setEmail(user.getUsername() + "@email.test");
|
||||||
|
user.setFirstName(firstName);
|
||||||
|
user.setLastName(lastName);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected UsersResource users(ContainerInfo node) {
|
||||||
|
return getAdminClientFor(node).realm(testRealmName).users();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserResource entityResource(UserRepresentation user, ContainerInfo node) {
|
||||||
|
return entityResource(user.getId(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserResource entityResource(String id, ContainerInfo node) {
|
||||||
|
return users(node).get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserRepresentation createEntity(UserRepresentation user, ContainerInfo node) {
|
||||||
|
Response response = users(node).create(user);
|
||||||
|
String id = ApiUtil.getCreatedId(response);
|
||||||
|
response.close();
|
||||||
|
user.setId(id);
|
||||||
|
return readEntity(user, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserRepresentation readEntity(UserRepresentation user, ContainerInfo node) {
|
||||||
|
UserRepresentation u = null;
|
||||||
|
try {
|
||||||
|
u = entityResource(user, node).toRepresentation();
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// expected when user doesn't exist
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserRepresentation updateEntity(UserRepresentation user, ContainerInfo node) {
|
||||||
|
entityResource(user, node).update(user);
|
||||||
|
return readEntity(user, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteEntity(UserRepresentation user, ContainerInfo node) {
|
||||||
|
entityResource(user, node).remove();
|
||||||
|
assertNull(readEntity(user, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserRepresentation testEntityUpdates(UserRepresentation user, boolean backendFailover) {
|
||||||
|
|
||||||
|
// username
|
||||||
|
user.setUsername(user.getUsername() + "_updated");
|
||||||
|
user = updateEntityOnCurrentFailNode(user, "username");
|
||||||
|
verifyEntityUpdateDuringFailover(user, backendFailover);
|
||||||
|
|
||||||
|
// first+lastName
|
||||||
|
user.setFirstName(user.getFirstName() + "_updated");
|
||||||
|
user.setLastName(user.getLastName() + "_updated");
|
||||||
|
user = updateEntityOnCurrentFailNode(user, "firstName/lastName");
|
||||||
|
verifyEntityUpdateDuringFailover(user, backendFailover);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -42,7 +42,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"theme": {
|
"theme": {
|
||||||
"default": "keycloak",
|
|
||||||
"staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}",
|
"staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}",
|
||||||
"cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
|
"cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
|
||||||
"cacheThemes": "${keycloak.theme.cacheThemes:true}",
|
"cacheThemes": "${keycloak.theme.cacheThemes:true}",
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext">
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext">
|
||||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||||
</NameIDFormat>
|
</NameIDFormat>
|
||||||
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/sales-metadata/"/>
|
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/sales-metadata/"/>
|
||||||
<AssertionConsumerService
|
<AssertionConsumerService
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/sales-metadata/"
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/sales-metadata/"
|
||||||
index="1" isDefault="true" />
|
index="1" isDefault="true" />
|
||||||
<KeyDescriptor use="signing">
|
<KeyDescriptor use="signing">
|
||||||
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
|
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
|
|
@ -172,8 +172,8 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8080/bad-realm-sales-post-sig/",
|
"baseUrl": "http://localhost:8080/bad-realm-sales-post-sig",
|
||||||
"adminUrl": "http://localhost:8080/bad-realm-sales-post-sig/",
|
"adminUrl": "http://localhost:8080/bad-realm-sales-post-sig",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/bad-realm-sales-post-sig/*"
|
"http://localhost:8080/bad-realm-sales-post-sig/*"
|
||||||
],
|
],
|
||||||
|
@ -189,8 +189,8 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8080/bad-client-sales-post-sig/",
|
"baseUrl": "http://localhost:8080/bad-client-sales-post-sig",
|
||||||
"adminUrl": "http://localhost:8080/bad-client-sales-post-sig/",
|
"adminUrl": "http://localhost:8080/bad-client-sales-post-sig",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/bad-client-sales-post-sig/*"
|
"http://localhost:8080/bad-client-sales-post-sig/*"
|
||||||
],
|
],
|
||||||
|
@ -229,7 +229,7 @@
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/employee-sig/*"
|
"http://localhost:8080/employee-sig/*"
|
||||||
],
|
],
|
||||||
"adminUrl": "http://localhost:8080/employee-sig/",
|
"adminUrl": "http://localhost:8080/employee-sig",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
|
@ -243,11 +243,11 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8080/employee/",
|
"baseUrl": "http://localhost:8080/employee",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/employee/*"
|
"http://localhost:8080/employee/*"
|
||||||
],
|
],
|
||||||
"adminUrl": "http://localhost:8080/employee/",
|
"adminUrl": "http://localhost:8080/employee",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.authnstatement": "true"
|
"saml.authnstatement": "true"
|
||||||
},
|
},
|
||||||
|
@ -293,11 +293,11 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8080/employee2/",
|
"baseUrl": "http://localhost:8080/employee2",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/employee2/*"
|
"http://localhost:8080/employee2/*"
|
||||||
],
|
],
|
||||||
"adminUrl": "http://localhost:8080/employee2/",
|
"adminUrl": "http://localhost:8080/employee2",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.authnstatement": "true"
|
"saml.authnstatement": "true"
|
||||||
},
|
},
|
||||||
|
@ -344,7 +344,7 @@
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"frontchannelLogout": true,
|
"frontchannelLogout": true,
|
||||||
"baseUrl": "http://localhost:8080/employee-sig-front/",
|
"baseUrl": "http://localhost:8080/employee-sig-front",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/employee-sig-front/*"
|
"http://localhost:8080/employee-sig-front/*"
|
||||||
],
|
],
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||||
-Djava.net.preferIPv4Stack=true
|
-Djava.net.preferIPv4Stack=true
|
||||||
</property>
|
</property>
|
||||||
|
<property name="outputToConsole">${frontend.console.output}</property>
|
||||||
<property name="managementPort">${auth.server.management.port}</property>
|
<property name="managementPort">${auth.server.management.port}</property>
|
||||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||||
-Djava.net.preferIPv4Stack=true
|
-Djava.net.preferIPv4Stack=true
|
||||||
</property>
|
</property>
|
||||||
<!--<property name="outputToConsole">false</property>-->
|
<property name="outputToConsole">${backends.console.output}</property>
|
||||||
<property name="managementPort">${auth.server.backend1.management.port}</property>
|
<property name="managementPort">${auth.server.backend1.management.port}</property>
|
||||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -118,7 +119,7 @@
|
||||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||||
-Djava.net.preferIPv4Stack=true
|
-Djava.net.preferIPv4Stack=true
|
||||||
</property>
|
</property>
|
||||||
<!--<property name="outputToConsole">false</property>-->
|
<property name="outputToConsole">${backends.console.output}</property>
|
||||||
<property name="managementPort">${auth.server.backend2.management.port}</property>
|
<property name="managementPort">${auth.server.backend2.management.port}</property>
|
||||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -30,20 +30,20 @@ public class ModAuthMellonTest extends AbstractAuthTest {
|
||||||
@Test
|
@Test
|
||||||
public void modAuthMellonTest() throws TransformerException {
|
public void modAuthMellonTest() throws TransformerException {
|
||||||
testRealmPage.setAuthRealm("mellon-test");
|
testRealmPage.setAuthRealm("mellon-test");
|
||||||
testRealmSAMLLoginPage.setAuthRealm("mellon-test");
|
testRealmSAMLRedirectLoginPage.setAuthRealm("mellon-test");
|
||||||
|
|
||||||
modAuthMellonUnprotectedResourcePage.navigateTo();
|
modAuthMellonUnprotectedResourcePage.navigateTo();
|
||||||
assertTrue(driver.getPageSource().contains("Unprotected resource"));
|
assertTrue(driver.getPageSource().contains("Unprotected resource"));
|
||||||
|
|
||||||
modAuthMellonProtectedResourcePage.navigateTo();
|
modAuthMellonProtectedResourcePage.navigateTo();
|
||||||
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
testRealmSAMLLoginPage.form().login(bburkeUser);
|
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
|
||||||
assertTrue(driver.getPageSource().contains("Protected resource"));
|
assertTrue(driver.getPageSource().contains("Protected resource"));
|
||||||
|
|
||||||
modAuthMellonProtectedResourcePage.logout();
|
modAuthMellonProtectedResourcePage.logout();
|
||||||
assertTrue(driver.getPageSource().contains("Unprotected resource"));
|
assertTrue(driver.getPageSource().contains("Unprotected resource"));
|
||||||
|
|
||||||
modAuthMellonProtectedResourcePage.navigateTo();
|
modAuthMellonProtectedResourcePage.navigateTo();
|
||||||
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLLoginPage);
|
URLAssert.assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,9 @@
|
||||||
<arquillian-wildfly-container.version>8.2.0.Final</arquillian-wildfly-container.version>
|
<arquillian-wildfly-container.version>8.2.0.Final</arquillian-wildfly-container.version>
|
||||||
<version.shrinkwrap.resolvers>2.1.1</version.shrinkwrap.resolvers>
|
<version.shrinkwrap.resolvers>2.1.1</version.shrinkwrap.resolvers>
|
||||||
|
|
||||||
|
<frontend.console.output>true</frontend.console.output>
|
||||||
|
<backends.console.output>true</backends.console.output>
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -119,6 +122,8 @@
|
||||||
<auth.server.ssl.required>${auth.server.ssl.required}</auth.server.ssl.required>
|
<auth.server.ssl.required>${auth.server.ssl.required}</auth.server.ssl.required>
|
||||||
<startup.timeout.sec>${startup.timeout.sec}</startup.timeout.sec>
|
<startup.timeout.sec>${startup.timeout.sec}</startup.timeout.sec>
|
||||||
<jboss.server.config.dir>${jboss.server.config.dir}</jboss.server.config.dir>
|
<jboss.server.config.dir>${jboss.server.config.dir}</jboss.server.config.dir>
|
||||||
|
<frontend.console.output>${frontend.console.output}</frontend.console.output>
|
||||||
|
<backends.console.output>${backend.console.output}</backends.console.output>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<properties>
|
<properties>
|
||||||
<property>
|
<property>
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
||||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
import org.keycloak.testsuite.util.cli.InfinispanCLI;
|
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
|
@ -206,8 +206,8 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (System.getProperties().containsKey("startInfinispanCLI")) {
|
if (System.getProperties().containsKey("startTestsuiteCLI")) {
|
||||||
new InfinispanCLI(keycloak).start();
|
new TestsuiteCLI(keycloak).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
return keycloak;
|
return keycloak;
|
||||||
|
|
|
@ -43,23 +43,19 @@ import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.testsuite.federation.ldap.FederationTestUtils;
|
import org.keycloak.testsuite.federation.ldap.FederationTestUtils;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.LDAPRule;
|
import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
|
||||||
import org.keycloak.timer.TimerProvider;
|
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class SyncProvidersTest {
|
public class LDAPSyncTest {
|
||||||
|
|
||||||
private static LDAPRule ldapRule = new LDAPRule();
|
private static LDAPRule ldapRule = new LDAPRule();
|
||||||
|
|
||||||
private static UserFederationProviderModel ldapModel = null;
|
private static UserFederationProviderModel ldapModel = null;
|
||||||
private static UserFederationProviderModel dummyModel = null;
|
|
||||||
|
|
||||||
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@ -84,9 +80,6 @@ public class SyncProvidersTest {
|
||||||
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
|
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
|
||||||
FederationTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1");
|
FederationTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dummy provider
|
|
||||||
dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -365,37 +358,6 @@ public class SyncProvidersTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPeriodicSync() {
|
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
|
||||||
try {
|
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
|
||||||
DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
|
|
||||||
int full = dummyFedFactory.getFullSyncCounter();
|
|
||||||
int changed = dummyFedFactory.getChangedSyncCounter();
|
|
||||||
|
|
||||||
// Assert that after some period was DummyUserFederationProvider triggered
|
|
||||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
|
||||||
usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
|
|
||||||
sleep(1800);
|
|
||||||
|
|
||||||
// Cancel timer
|
|
||||||
usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel);
|
|
||||||
|
|
||||||
// Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked
|
|
||||||
int newChanged = dummyFedFactory.getChangedSyncCounter();
|
|
||||||
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
|
|
||||||
Assert.assertTrue(newChanged > changed);
|
|
||||||
|
|
||||||
// Assert that dummy provider won't be invoked anymore
|
|
||||||
sleep(1800);
|
|
||||||
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
|
|
||||||
Assert.assertEquals(newChanged, dummyFedFactory.getChangedSyncCounter());
|
|
||||||
} finally {
|
|
||||||
keycloakRule.stopSession(session, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sleep(int time) {
|
private void sleep(int time) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(time);
|
Thread.sleep(time);
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.federation.sync;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class SyncDummyUserFederationProviderFactory extends DummyUserFederationProviderFactory {
|
||||||
|
|
||||||
|
// Used during SyncFederationTest
|
||||||
|
static volatile CountDownLatch latch1 = new CountDownLatch(1);
|
||||||
|
static volatile CountDownLatch latch2 = new CountDownLatch(1);
|
||||||
|
|
||||||
|
static void restartLatches() {
|
||||||
|
latch1 = new CountDownLatch(1);
|
||||||
|
latch2 = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(SyncDummyUserFederationProviderFactory.class);
|
||||||
|
|
||||||
|
public static final String SYNC_PROVIDER_ID = "sync-dummy";
|
||||||
|
public static final String WAIT_TIME = "wait-time"; // waitTime before transaction is commited
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return SYNC_PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getConfigurationOptions() {
|
||||||
|
Set<String> list = super.getConfigurationOptions();
|
||||||
|
list.add(WAIT_TIME);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
|
||||||
|
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
int waitTime = Integer.parseInt(model.getConfig().get(WAIT_TIME));
|
||||||
|
|
||||||
|
logger.infof("Starting sync of changed users. Wait time is: %s", waitTime);
|
||||||
|
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// KEYCLOAK-2412 : Just remove and add some users for testing purposes
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
String username = "dummyuser-" + i;
|
||||||
|
UserModel user = session.userStorage().getUserByUsername(username, realm);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
session.userStorage().removeUser(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
user = session.userStorage().addUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.infof("Finished sync of changed users. Waiting now for %d seconds", waitTime);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
latch1.await(waitTime * 1000, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("Interrupted!", ie);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.infof("Finished waiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// countDown, so the SyncFederationTest can continue
|
||||||
|
latch2.countDown();
|
||||||
|
|
||||||
|
return new UserFederationSyncResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.federation.sync;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserFederationProvider;
|
||||||
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.services.managers.UsersSyncManager;
|
||||||
|
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test with Dummy providers (For LDAP see {@link org.keycloak.testsuite.federation.ldap.base.LDAPSyncTest}
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
public class SyncFederationTest {
|
||||||
|
|
||||||
|
private static UserFederationProviderModel dummyModel = null;
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
// Other tests may left Time offset uncleared, which could cause issues
|
||||||
|
Time.setOffset(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01PeriodicSync() {
|
||||||
|
|
||||||
|
// Enable timer for SyncDummyUserFederationProvider
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-sync-dummy", -1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
|
DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
|
||||||
|
int full = dummyFedFactory.getFullSyncCounter();
|
||||||
|
int changed = dummyFedFactory.getChangedSyncCounter();
|
||||||
|
|
||||||
|
// Assert that after some period was DummyUserFederationProvider triggered
|
||||||
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
|
usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
|
||||||
|
sleep(1800);
|
||||||
|
|
||||||
|
// Cancel timer
|
||||||
|
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||||
|
usersSyncManager.notifyToRefreshPeriodicSync(session, appRealm, dummyModel, true);
|
||||||
|
|
||||||
|
// Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked
|
||||||
|
int newChanged = dummyFedFactory.getChangedSyncCounter();
|
||||||
|
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
|
||||||
|
Assert.assertTrue(newChanged > changed);
|
||||||
|
|
||||||
|
// Assert that dummy provider won't be invoked anymore
|
||||||
|
sleep(1800);
|
||||||
|
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
|
||||||
|
Assert.assertEquals(newChanged, dummyFedFactory.getChangedSyncCounter());
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove dummyProvider
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.removeUserFederationProvider(dummyModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test02ConcurrentSync() throws Exception {
|
||||||
|
SyncDummyUserFederationProviderFactory.restartLatches();
|
||||||
|
|
||||||
|
// Enable timer for SyncDummyUserFederationProvider
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
Map<String, String> config = new HashMap<>();
|
||||||
|
config.put(SyncDummyUserFederationProviderFactory.WAIT_TIME, "2000");
|
||||||
|
dummyModel = appRealm.addUserFederationProvider(SyncDummyUserFederationProviderFactory.SYNC_PROVIDER_ID, config, 1, "test-sync-dummy", -1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
|
|
||||||
|
// bootstrap periodic sync
|
||||||
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
|
usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
|
||||||
|
|
||||||
|
// Wait and then trigger sync manually. Assert it will be ignored
|
||||||
|
sleep(1800);
|
||||||
|
RealmModel realm = session.realms().getRealm("test");
|
||||||
|
UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, realm.getId(), dummyModel);
|
||||||
|
Assert.assertTrue(syncResult.isIgnored());
|
||||||
|
|
||||||
|
// Cancel timer
|
||||||
|
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, dummyModel, true);
|
||||||
|
|
||||||
|
// Signal to factory to finish waiting
|
||||||
|
SyncDummyUserFederationProviderFactory.latch1.countDown();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncDummyUserFederationProviderFactory.latch2.await(20000, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// remove provider
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.removeUserFederationProvider(dummyModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleep(int time) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(time);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
@ -83,7 +84,7 @@ public class UserSessionInitializerTest {
|
||||||
|
|
||||||
// Create and persist offline sessions
|
// Create and persist offline sessions
|
||||||
int started = Time.currentTime();
|
int started = Time.currentTime();
|
||||||
int serverStartTime = session.sessions().getClusterStartupTime();
|
int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
|
||||||
|
|
||||||
for (UserSessionModel origSession : origSessions) {
|
for (UserSessionModel origSession : origSessions) {
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
|
||||||
|
@ -99,7 +100,7 @@ public class UserSessionInitializerTest {
|
||||||
|
|
||||||
// Clear ispn cache to ensure initializerState is removed as well
|
// Clear ispn cache to ensure initializerState is removed as well
|
||||||
InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
infinispan.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME).clear();
|
infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
|
||||||
|
|
||||||
resetSession();
|
resetSession();
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ public abstract class AbstractCommand {
|
||||||
protected List<String> args;
|
protected List<String> args;
|
||||||
protected KeycloakSessionFactory sessionFactory;
|
protected KeycloakSessionFactory sessionFactory;
|
||||||
|
|
||||||
public void injectProperties(List<String> args, InfinispanCLI cli, KeycloakSessionFactory sessionFactory) {
|
public void injectProperties(List<String> args, TestsuiteCLI cli, KeycloakSessionFactory sessionFactory) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,32 @@ public class PersistSessionsCommand extends AbstractCommand {
|
||||||
@Override
|
@Override
|
||||||
public void doRunCommand(KeycloakSession sess) {
|
public void doRunCommand(KeycloakSession sess) {
|
||||||
final int count = getIntArg(0);
|
final int count = getIntArg(0);
|
||||||
|
final int batchCount = getIntArg(1);
|
||||||
|
|
||||||
|
int remaining = count;
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
int createInThisBatch = Math.min(batchCount, remaining);
|
||||||
|
createSessionsBatch(createInThisBatch);
|
||||||
|
remaining = remaining - createInThisBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write some summary
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
log.info("Command finished. Total number of sessions in persister: " + persister.getUserSessionsCount(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSessionsBatch(final int countInThisBatch) {
|
||||||
final List<String> userSessionIds = new LinkedList<>();
|
final List<String> userSessionIds = new LinkedList<>();
|
||||||
final List<String> clientSessionIds = new LinkedList<>();
|
final List<String> clientSessionIds = new LinkedList<>();
|
||||||
|
|
||||||
// Create sessions in separate transaction first
|
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,7 +78,7 @@ public class PersistSessionsCommand extends AbstractCommand {
|
||||||
ClientModel testApp = realm.getClientByClientId("security-admin-console");
|
ClientModel testApp = realm.getClientByClientId("security-admin-console");
|
||||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < countInThisBatch; i++) {
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
|
||||||
clientSession.setUserSession(userSession);
|
clientSession.setUserSession(userSession);
|
||||||
|
@ -69,9 +91,10 @@ public class PersistSessionsCommand extends AbstractCommand {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Sessions created in infinispan storage");
|
log.infof("%d sessions created in infinispan storage", countInThisBatch);
|
||||||
|
|
||||||
// Persist them now
|
// Persist them now
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,35 +107,17 @@ public class PersistSessionsCommand extends AbstractCommand {
|
||||||
counter++;
|
counter++;
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
|
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
|
||||||
persister.createUserSession(userSession, true);
|
persister.createUserSession(userSession, true);
|
||||||
if (counter%1000 == 0) {
|
|
||||||
log.infof("%d user sessions persisted. Continue", counter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.infof("All %d user sessions persisted", counter);
|
log.infof("%d user sessions persisted. Continue", counter);
|
||||||
|
|
||||||
counter = 0;
|
counter = 0;
|
||||||
for (String clientSessionId : clientSessionIds) {
|
for (String clientSessionId : clientSessionIds) {
|
||||||
counter++;
|
counter++;
|
||||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
|
ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
|
||||||
persister.createClientSession(clientSession, true);
|
persister.createClientSession(clientSession, true);
|
||||||
if (counter%1000 == 0) {
|
|
||||||
log.infof("%d client sessions persisted. Continue", counter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
log.infof("%d client sessions persisted. Continue", counter);
|
||||||
log.infof("All %d client sessions persisted", counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Persist them now
|
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(KeycloakSession session) {
|
|
||||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
log.info("Total number of sessions in persister: " + persister.getUserSessionsCount(true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -120,6 +125,6 @@ public class PersistSessionsCommand extends AbstractCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String printUsage() {
|
public String printUsage() {
|
||||||
return super.printUsage() + " <sessions-count>";
|
return super.printUsage() + " <sessions-count> <sessions-count-per-each-transaction>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.util.cli;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.services.managers.UsersSyncManager;
|
||||||
|
import org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory;
|
||||||
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class SyncDummyFederationProviderCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRunCommand(KeycloakSession session) {
|
||||||
|
int waitTime = getIntArg(0);
|
||||||
|
int changedSyncPeriod = getIntArg(1);
|
||||||
|
|
||||||
|
RealmModel realm = session.realms().getRealmByName("master");
|
||||||
|
UserFederationProviderModel fedProviderModel = KeycloakModelUtils.findUserFederationProviderByDisplayName("cluster-dummy", realm);
|
||||||
|
if (fedProviderModel == null) {
|
||||||
|
Map<String, String> cfg = new HashMap<>();
|
||||||
|
updateConfig(cfg, waitTime);
|
||||||
|
fedProviderModel = realm.addUserFederationProvider(SyncDummyUserFederationProviderFactory.SYNC_PROVIDER_ID, cfg, 1, "cluster-dummy", -1, changedSyncPeriod, -1);
|
||||||
|
} else {
|
||||||
|
Map<String, String> cfg = fedProviderModel.getConfig();
|
||||||
|
updateConfig(cfg, waitTime);
|
||||||
|
fedProviderModel.setChangedSyncPeriod(changedSyncPeriod);
|
||||||
|
realm.updateUserFederationProvider(fedProviderModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, fedProviderModel, false);
|
||||||
|
|
||||||
|
log.infof("User federation provider created and sync was started", waitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfig(Map<String, String> cfg, int waitTime) {
|
||||||
|
cfg.put(SyncDummyUserFederationProviderFactory.WAIT_TIME, String.valueOf(waitTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "startSyncDummy";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String printUsage() {
|
||||||
|
return super.printUsage() + " <wait-time-before-sync-commit-in-seconds> <changed-sync-period-in-seconds>";
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,27 +32,13 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.testsuite.KeycloakServer;
|
import org.keycloak.testsuite.KeycloakServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HOWTO USE THIS:
|
* See Testsuite.md (section how to create many users and offline sessions)
|
||||||
*
|
|
||||||
* 1) Run KeycloakServer with system properties (assuming mongo up and running on localhost):
|
|
||||||
* -Dkeycloak.realm.provider=mongo -Dkeycloak.user.provider=mongo -Dkeycloak.userSessionPersister.provider=mongo -Dkeycloak.connectionsMongo.db=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dresources -DstartInfinispanCLI
|
|
||||||
*
|
|
||||||
* 2) Write command on STDIN to persist 50000 userSessions to mongo: persistSessions 50000
|
|
||||||
*
|
|
||||||
* 3) Run command "clear" to ensure infinispan cache is cleared. Doublecheck with command "size" is 0
|
|
||||||
*
|
|
||||||
* 4) Write command to load sessions from persistent storage - 100 sessions per worker transaction: loadPersistentSessions 100
|
|
||||||
*
|
|
||||||
* See the progress in log. Finally run command "size" to ensure size is 100001 (50000 userSessions + 50000 clientSessions + 1 initializationState item)
|
|
||||||
*
|
|
||||||
* 5) Alternative to step 3+4 - Kill the server after step 2 and start two KeycloakServer in parallel on ports 8081 and 8082 . See the progress in logs of loading persistent sessions to infinispan.
|
|
||||||
* Kill the coordinator (usually 8081 node) during startup and see the node 8082 became coordinator and took ownership of loading persistent sessions. After node 8082 fully started, the size of infinispan is again 100001
|
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class InfinispanCLI {
|
public class TestsuiteCLI {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(InfinispanCLI.class);
|
private static final Logger log = Logger.getLogger(TestsuiteCLI.class);
|
||||||
|
|
||||||
private static final Class<?>[] BUILTIN_COMMANDS = {
|
private static final Class<?>[] BUILTIN_COMMANDS = {
|
||||||
ExitCommand.class,
|
ExitCommand.class,
|
||||||
|
@ -69,13 +55,14 @@ public class InfinispanCLI {
|
||||||
UserCommands.Create.class,
|
UserCommands.Create.class,
|
||||||
UserCommands.Remove.class,
|
UserCommands.Remove.class,
|
||||||
UserCommands.Count.class,
|
UserCommands.Count.class,
|
||||||
UserCommands.GetUser.class
|
UserCommands.GetUser.class,
|
||||||
|
SyncDummyFederationProviderCommand.class
|
||||||
};
|
};
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
private final Map<String, Class<? extends AbstractCommand>> commands = new LinkedHashMap<>();
|
private final Map<String, Class<? extends AbstractCommand>> commands = new LinkedHashMap<>();
|
||||||
|
|
||||||
public InfinispanCLI(KeycloakServer server) {
|
public TestsuiteCLI(KeycloakServer server) {
|
||||||
this.sessionFactory = server.getSessionFactory();
|
this.sessionFactory = server.getSessionFactory();
|
||||||
|
|
||||||
// register builtin commands
|
// register builtin commands
|
||||||
|
@ -96,7 +83,7 @@ public class InfinispanCLI {
|
||||||
|
|
||||||
// WARNING: Stdin blocking operation
|
// WARNING: Stdin blocking operation
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
log.info("Starting infinispan CLI. Exit with 'exit' . Available commands with 'help' ");
|
log.info("Starting testsuite CLI. Exit with 'exit' . Available commands with 'help' ");
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
String line;
|
String line;
|
||||||
|
@ -131,7 +118,7 @@ public class InfinispanCLI {
|
||||||
System.out.print("$ ");
|
System.out.print("$ ");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
log.info("Exit infinispan CLI");
|
log.info("Exit testsuite CLI");
|
||||||
reader.close();
|
reader.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +151,7 @@ public class InfinispanCLI {
|
||||||
private List<String> commandNames = new ArrayList<>();
|
private List<String> commandNames = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectProperties(List<String> args, InfinispanCLI cli, KeycloakSessionFactory sessionFactory) {
|
public void injectProperties(List<String> args, TestsuiteCLI cli, KeycloakSessionFactory sessionFactory) {
|
||||||
for (String commandName : cli.commands.keySet()) {
|
for (String commandName : cli.commands.keySet()) {
|
||||||
commandNames.add(commandName);
|
commandNames.add(commandName);
|
||||||
}
|
}
|
|
@ -37,7 +37,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"theme": {
|
"theme": {
|
||||||
"default": "keycloak",
|
|
||||||
"staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}",
|
"staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}",
|
||||||
"cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
|
"cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
|
||||||
"cacheThemes": "${keycloak.theme.cacheThemes:true}",
|
"cacheThemes": "${keycloak.theme.cacheThemes:true}",
|
||||||
|
|
|
@ -15,4 +15,5 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.testsuite.DummyUserFederationProviderFactory
|
org.keycloak.testsuite.DummyUserFederationProviderFactory
|
||||||
|
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
|
|
@ -62,6 +62,7 @@ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcesso
|
||||||
st.addDependency(cacheContainerService.append("sessions"));
|
st.addDependency(cacheContainerService.append("sessions"));
|
||||||
st.addDependency(cacheContainerService.append("offlineSessions"));
|
st.addDependency(cacheContainerService.append("offlineSessions"));
|
||||||
st.addDependency(cacheContainerService.append("loginFailures"));
|
st.addDependency(cacheContainerService.append("loginFailures"));
|
||||||
|
st.addDependency(cacheContainerService.append("work"));
|
||||||
st.addDependency(cacheContainerService.append("realmVersions"));
|
st.addDependency(cacheContainerService.append("realmVersions"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<local-cache name="sessions"/>
|
<local-cache name="sessions"/>
|
||||||
<local-cache name="offlineSessions"/>
|
<local-cache name="offlineSessions"/>
|
||||||
<local-cache name="loginFailures"/>
|
<local-cache name="loginFailures"/>
|
||||||
|
<local-cache name="work"/>
|
||||||
<local-cache name="realmVersions">
|
<local-cache name="realmVersions">
|
||||||
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
||||||
</local-cache>
|
</local-cache>
|
||||||
|
@ -90,6 +91,7 @@
|
||||||
<distributed-cache name="sessions" mode="SYNC" owners="1"/>
|
<distributed-cache name="sessions" mode="SYNC" owners="1"/>
|
||||||
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
|
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
|
||||||
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
|
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
|
||||||
|
<replicated-cache name="work" mode="SYNC" />
|
||||||
<local-cache name="realmVersions">
|
<local-cache name="realmVersions">
|
||||||
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
||||||
</local-cache>
|
</local-cache>
|
||||||
|
|
Loading…
Reference in a new issue