user spi cache policy
This commit is contained in:
parent
fd86f3bda8
commit
3e28ac1e46
13 changed files with 633 additions and 38 deletions
|
@ -13,6 +13,7 @@ import java.util.HashSet;
|
|||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
|
@ -126,6 +127,10 @@ public abstract class CacheManager {
|
|||
}
|
||||
|
||||
public void addRevisioned(Revisioned object, long startupRevision) {
|
||||
addRevisioned(object, startupRevision, -1);
|
||||
}
|
||||
|
||||
public void addRevisioned(Revisioned object, long startupRevision, long lifespan) {
|
||||
//startRevisionBatch();
|
||||
String id = object.getId();
|
||||
try {
|
||||
|
@ -164,7 +169,8 @@ public abstract class CacheManager {
|
|||
// revisions cache has a lower value than the object.revision, so update revision and add it to cache
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
|
||||
revisions.put(id, object.getRevision());
|
||||
cache.putForExternalRead(id, object);
|
||||
if (lifespan < 0) cache.putForExternalRead(id, object);
|
||||
else cache.putForExternalRead(id, object, lifespan, TimeUnit.MILLISECONDS);
|
||||
} finally {
|
||||
endRevisionBatch();
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ package org.keycloak.models.cache.infinispan;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -31,7 +31,6 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
|
@ -43,8 +42,13 @@ import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
|||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsents;
|
||||
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -177,22 +181,19 @@ public class UserCacheSession implements UserCache {
|
|||
}
|
||||
|
||||
CachedUser cached = cache.get(id, CachedUser.class);
|
||||
UserModel delegate = null;
|
||||
boolean wasCached = cached != null;
|
||||
UserModel adapter = null;
|
||||
if (cached == null) {
|
||||
logger.trace("not cached");
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
delegate = getDelegate().getUserById(id, realm);
|
||||
UserModel delegate = getDelegate().getUserById(id, realm);
|
||||
if (delegate == null) {
|
||||
logger.trace("delegate returning null");
|
||||
return null;
|
||||
}
|
||||
cached = new CachedUser(loaded, realm, delegate);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
adapter = cacheUser(realm, delegate, loaded);
|
||||
} else {
|
||||
adapter = validateCache(realm, cached);
|
||||
}
|
||||
logger.trace("returning new cache adapter");
|
||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||
if (!wasCached) onCache(realm, adapter, delegate);
|
||||
managedUsers.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
@ -238,15 +239,17 @@ public class UserCacheSession implements UserCache {
|
|||
return null;
|
||||
}
|
||||
userId = model.getId();
|
||||
query = new UserListQuery(loaded, cacheKey, realm, model.getId());
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
if (invalidations.contains(userId)) return model;
|
||||
if (managedUsers.containsKey(userId)) {
|
||||
logger.tracev("return managed user");
|
||||
return managedUsers.get(userId);
|
||||
}
|
||||
|
||||
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
UserModel adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
if (adapter instanceof UserAdapter) { // this was cached, so we can cache query too
|
||||
query = new UserListQuery(loaded, cacheKey, realm, model.getId());
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
}
|
||||
managedUsers.put(userId, adapter);
|
||||
return adapter;
|
||||
} else {
|
||||
|
@ -261,21 +264,132 @@ public class UserCacheSession implements UserCache {
|
|||
}
|
||||
}
|
||||
|
||||
protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel delegate) {
|
||||
protected UserModel getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel delegate) {
|
||||
CachedUser cached = cache.get(userId, CachedUser.class);
|
||||
boolean wasCached = cached != null;
|
||||
if (cached == null) {
|
||||
cached = new CachedUser(loaded, realm, delegate);
|
||||
return cacheUser(realm, delegate, loaded);
|
||||
} else {
|
||||
return validateCache(realm, cached);
|
||||
}
|
||||
}
|
||||
|
||||
protected UserModel validateCache(RealmModel realm, CachedUser cached) {
|
||||
StorageId storageId = new StorageId(cached.getId());
|
||||
if (!storageId.isLocal()) {
|
||||
ComponentModel component = realm.getComponent(storageId.getProviderId());
|
||||
UserStorageProviderModel model = new UserStorageProviderModel(component);
|
||||
UserStorageProviderModel.CachePolicy policy = model.getCachePolicy();
|
||||
// although we do set a timeout, Infinispan has no guarantees when the user will be evicted
|
||||
// its also hard to test stuff
|
||||
boolean invalidate = false;
|
||||
if (policy != null) {
|
||||
String currentTime = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(Time.currentTimeMillis()));
|
||||
if (policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||
invalidate = true;
|
||||
} else if (cached.getCacheTimestamp() < model.getCacheInvalidBefore()) {
|
||||
invalidate = true;
|
||||
} else if (policy == UserStorageProviderModel.CachePolicy.EVICT_DAILY) {
|
||||
long dailyTimeout = dailyTimeout(model.getEvictionHour(), model.getEvictionMinute());
|
||||
dailyTimeout = dailyTimeout - (24 * 60 * 60 * 1000);
|
||||
//String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(dailyTimeout));
|
||||
//String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
|
||||
if (cached.getCacheTimestamp() <= dailyTimeout) {
|
||||
invalidate = true;
|
||||
}
|
||||
} else if (policy == UserStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
|
||||
int oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||
long weeklyTimeout = weeklyTimeout(model.getEvictionDay(), model.getEvictionHour(), model.getEvictionMinute());
|
||||
long lastTimeout = weeklyTimeout - oneWeek;
|
||||
String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(weeklyTimeout));
|
||||
String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
|
||||
if (cached.getCacheTimestamp() <= lastTimeout) {
|
||||
invalidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidate) {
|
||||
registerUserInvalidation(realm, cached);
|
||||
return getDelegate().getUserById(cached.getId(), realm);
|
||||
}
|
||||
}
|
||||
return new UserAdapter(cached, this, session, realm);
|
||||
}
|
||||
|
||||
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
||||
StorageId storageId = new StorageId(delegate.getId());
|
||||
CachedUser cached = null;
|
||||
if (!storageId.isLocal()) {
|
||||
ComponentModel component = realm.getComponent(storageId.getProviderId());
|
||||
UserStorageProviderModel model = new UserStorageProviderModel(component);
|
||||
UserStorageProviderModel.CachePolicy policy = model.getCachePolicy();
|
||||
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||
return delegate;
|
||||
}
|
||||
cached = new CachedUser(revision, realm, delegate);
|
||||
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else {
|
||||
long lifespan = -1;
|
||||
if (policy == UserStorageProviderModel.CachePolicy.EVICT_DAILY) {
|
||||
if (model.getEvictionHour() > -1 && model.getEvictionMinute() > -1) {
|
||||
lifespan = dailyTimeout(model.getEvictionHour(), model.getEvictionMinute()) - Time.currentTimeMillis();
|
||||
}
|
||||
} else if (policy == UserStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
|
||||
if (model.getEvictionDay() > 0 && model.getEvictionHour() > -1 && model.getEvictionMinute() > -1) {
|
||||
lifespan = weeklyTimeout(model.getEvictionDay(), model.getEvictionHour(), model.getEvictionMinute()) - Time.currentTimeMillis();
|
||||
}
|
||||
} else if (policy == UserStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
|
||||
lifespan = model.getMaxLifespan();
|
||||
}
|
||||
if (lifespan > 0) {
|
||||
cache.addRevisioned(cached, startupRevision, lifespan);
|
||||
} else {
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cached = new CachedUser(revision, realm, delegate);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
}
|
||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||
if (!wasCached) {
|
||||
onCache(realm, adapter, delegate);
|
||||
}
|
||||
onCache(realm, adapter, delegate);
|
||||
return adapter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static long dailyTimeout(int hour, int minute) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal2.set(Calendar.MINUTE, minute);
|
||||
if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
|
||||
int add = (24 * 60 * 60 * 1000);
|
||||
cal.add(Calendar.MILLISECOND, add);
|
||||
} else {
|
||||
cal.add(Calendar.MILLISECOND, (int)(cal2.getTimeInMillis() - cal.getTimeInMillis()));
|
||||
}
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
public static long weeklyTimeout(int day, int hour, int minute) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal2.set(Calendar.MINUTE, minute);
|
||||
cal2.set(Calendar.DAY_OF_WEEK, day);
|
||||
if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
|
||||
int add = (7 * 24 * 60 * 60 * 1000);
|
||||
cal2.add(Calendar.MILLISECOND, add);
|
||||
}
|
||||
|
||||
return cal2.getTimeInMillis();
|
||||
}
|
||||
|
||||
private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) {
|
||||
((OnUserCache)getDelegate()).onCache(realm, adapter, delegate);
|
||||
((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate);
|
||||
|
@ -300,12 +414,14 @@ public class UserCacheSession implements UserCache {
|
|||
UserModel model = getDelegate().getUserByEmail(email, realm);
|
||||
if (model == null) return null;
|
||||
userId = model.getId();
|
||||
query = new UserListQuery(loaded, cacheKey, realm, model.getId());
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
if (invalidations.contains(userId)) return model;
|
||||
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
||||
|
||||
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
UserModel adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
if (adapter instanceof UserAdapter) {
|
||||
query = new UserListQuery(loaded, cacheKey, realm, model.getId());
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
}
|
||||
managedUsers.put(userId, adapter);
|
||||
return adapter;
|
||||
} else {
|
||||
|
@ -343,12 +459,15 @@ public class UserCacheSession implements UserCache {
|
|||
UserModel model = getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||
if (model == null) return null;
|
||||
userId = model.getId();
|
||||
query = new UserListQuery(loaded, cacheKey, realm, userId);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
if (invalidations.contains(userId)) return model;
|
||||
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
||||
|
||||
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
UserModel adapter = getUserAdapter(realm, userId, loaded, model);
|
||||
if (adapter instanceof UserAdapter) {
|
||||
query = new UserListQuery(loaded, cacheKey, realm, model.getId());
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
}
|
||||
|
||||
managedUsers.put(userId, adapter);
|
||||
return adapter;
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.models.cache.infinispan.entities;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
|
@ -9,7 +11,7 @@ import java.io.Serializable;
|
|||
public class AbstractRevisioned implements Revisioned, Serializable {
|
||||
private String id;
|
||||
private Long revision;
|
||||
private final long cacheTimestamp = System.currentTimeMillis();
|
||||
private final long cacheTimestamp = Time.currentTimeMillis();
|
||||
|
||||
public AbstractRevisioned(Long revision, String id) {
|
||||
this.revision = revision;
|
||||
|
|
|
@ -19,7 +19,11 @@ package org.keycloak.models.sessions.infinispan.initializer;
|
|||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.cache.infinispan.UserCacheSession;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -59,4 +63,31 @@ public class InitializerStateTest {
|
|||
Assert.assertTrue(segments.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDailyTimeout() throws Exception {
|
||||
Date date = new Date(UserCacheSession.dailyTimeout(10, 30));
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
date = new Date(UserCacheSession.dailyTimeout(17, 45));
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
date = new Date(UserCacheSession.weeklyTimeout(Calendar.MONDAY, 13, 45));
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
date = new Date(UserCacheSession.weeklyTimeout(Calendar.THURSDAY, 13, 45));
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
System.out.println("----");
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.HOUR, 1);
|
||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||
int min = cal.get(Calendar.MINUTE);
|
||||
date = new Date(cal.getTimeInMillis());
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
date = new Date(UserCacheSession.dailyTimeout(hour, min));
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
cal = Calendar.getInstance();
|
||||
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
|
||||
date = new Date(cal.getTimeInMillis());
|
||||
System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -810,6 +810,8 @@ public class ModelToRepresentation {
|
|||
} else {
|
||||
config.put(e.getKey(), e.getValue());
|
||||
}
|
||||
} else {
|
||||
config.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1757,18 +1757,9 @@ public class RepresentationToModel {
|
|||
component.setSubType(rep.getSubType());
|
||||
}
|
||||
|
||||
Map<String, ProviderConfigProperty> providerConfiguration = null;
|
||||
if (!internal) {
|
||||
providerConfiguration = ComponentUtil.getComponentConfigProperties(session, component);
|
||||
}
|
||||
|
||||
if (rep.getConfig() != null) {
|
||||
Set<String> keys = new HashSet<>(rep.getConfig().keySet());
|
||||
for (String k : keys) {
|
||||
if (!internal && !providerConfiguration.containsKey(k)) {
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> values = rep.getConfig().get(k);
|
||||
if (values == null || values.isEmpty() || values.get(0) == null || values.get(0).trim().isEmpty()) {
|
||||
component.getConfig().remove(k);
|
||||
|
|
|
@ -78,6 +78,10 @@ public class StorageId implements Serializable {
|
|||
public static boolean isLocalStorage(String userId) {
|
||||
return new StorageId(userId).getProviderId() == null;
|
||||
}
|
||||
public boolean isLocal() {
|
||||
return getProviderId() == null;
|
||||
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
|
|
@ -28,6 +28,14 @@ import org.keycloak.component.PrioritizedComponentModel;
|
|||
*/
|
||||
public class UserStorageProviderModel extends PrioritizedComponentModel {
|
||||
|
||||
public static enum CachePolicy {
|
||||
NO_CACHE,
|
||||
DEFAULT,
|
||||
EVICT_DAILY,
|
||||
EVICT_WEEKLY,
|
||||
MAX_LIFESPAN
|
||||
}
|
||||
|
||||
public UserStorageProviderModel() {
|
||||
setProviderType(UserStorageProvider.class.getName());
|
||||
}
|
||||
|
@ -40,6 +48,104 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
|
|||
private transient Integer changedSyncPeriod;
|
||||
private transient Integer lastSync;
|
||||
private transient Boolean importEnabled;
|
||||
private transient CachePolicy cachePolicy;
|
||||
private transient long maxLifespan = -1;
|
||||
private transient int evictionHour = -1;
|
||||
private transient int evictionMinute = -1;
|
||||
private transient int evictionDay = -1;
|
||||
private transient long cacheInvalidBefore = -1;
|
||||
|
||||
public CachePolicy getCachePolicy() {
|
||||
if (cachePolicy == null) {
|
||||
String str = getConfig().getFirst("cachePolicy");
|
||||
if (str == null) return null;
|
||||
cachePolicy = CachePolicy.valueOf(str);
|
||||
}
|
||||
return cachePolicy;
|
||||
}
|
||||
|
||||
public void setCachePolicy(CachePolicy cachePolicy) {
|
||||
this.cachePolicy = cachePolicy;
|
||||
if (cachePolicy == null) {
|
||||
getConfig().remove("cachePolicy");
|
||||
|
||||
} else {
|
||||
getConfig().putSingle("cachePolicy", cachePolicy.name());
|
||||
}
|
||||
}
|
||||
|
||||
public long getMaxLifespan() {
|
||||
if (maxLifespan < 0) {
|
||||
String str = getConfig().getFirst("maxLifespan");
|
||||
if (str == null) return -1;
|
||||
maxLifespan = Long.valueOf(str);
|
||||
}
|
||||
return maxLifespan;
|
||||
}
|
||||
|
||||
public void setMaxLifespan(long maxLifespan) {
|
||||
this.maxLifespan = maxLifespan;
|
||||
getConfig().putSingle("maxLifespan", Long.toString(maxLifespan));
|
||||
}
|
||||
|
||||
public int getEvictionHour() {
|
||||
if (evictionHour < 0) {
|
||||
String str = getConfig().getFirst("evictionHour");
|
||||
if (str == null) return -1;
|
||||
evictionHour = Integer.valueOf(str);
|
||||
}
|
||||
return evictionHour;
|
||||
}
|
||||
|
||||
public void setEvictionHour(int evictionHour) {
|
||||
if (evictionHour > 23 || evictionHour < 0) throw new IllegalArgumentException("Must be between 0 and 23");
|
||||
this.evictionHour = evictionHour;
|
||||
getConfig().putSingle("evictionHour", Integer.toString(evictionHour));
|
||||
}
|
||||
|
||||
public int getEvictionMinute() {
|
||||
if (evictionMinute < 0) {
|
||||
String str = getConfig().getFirst("evictionMinute");
|
||||
if (str == null) return -1;
|
||||
evictionMinute = Integer.valueOf(str);
|
||||
}
|
||||
return evictionMinute;
|
||||
}
|
||||
|
||||
public void setEvictionMinute(int evictionMinute) {
|
||||
if (evictionMinute > 59 || evictionMinute < 0) throw new IllegalArgumentException("Must be between 0 and 59");
|
||||
this.evictionMinute = evictionMinute;
|
||||
getConfig().putSingle("evictionMinute", Integer.toString(evictionMinute));
|
||||
}
|
||||
|
||||
public int getEvictionDay() {
|
||||
if (evictionDay < 0) {
|
||||
String str = getConfig().getFirst("evictionDay");
|
||||
if (str == null) return -1;
|
||||
evictionDay = Integer.valueOf(str);
|
||||
}
|
||||
return evictionDay;
|
||||
}
|
||||
|
||||
public void setEvictionDay(int evictionDay) {
|
||||
if (evictionDay > 7 || evictionDay < 1) throw new IllegalArgumentException("Must be between 1 and 7");
|
||||
this.evictionDay = evictionDay;
|
||||
getConfig().putSingle("evictionDay", Integer.toString(evictionDay));
|
||||
}
|
||||
|
||||
public long getCacheInvalidBefore() {
|
||||
if (cacheInvalidBefore < 0) {
|
||||
String str = getConfig().getFirst("cacheInvalidBefore");
|
||||
if (str == null) return -1;
|
||||
cacheInvalidBefore = Long.valueOf(str);
|
||||
}
|
||||
return cacheInvalidBefore;
|
||||
}
|
||||
|
||||
public void setCacheInvalidBefore(long cacheInvalidBefore) {
|
||||
this.cacheInvalidBefore = cacheInvalidBefore;
|
||||
getConfig().putSingle("cacheInvalidBefore", Long.toString(cacheInvalidBefore));
|
||||
}
|
||||
|
||||
public boolean isImportEnabled() {
|
||||
if (importEnabled == null) {
|
||||
|
@ -54,6 +160,8 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setImportEnabled(boolean flag) {
|
||||
importEnabled = flag;
|
||||
getConfig().putSingle("importEnabled", Boolean.toString(flag));
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
@ -88,6 +89,7 @@ public class ComponentResource {
|
|||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<ComponentRepresentation> getComponents(@QueryParam("parent") String parent, @QueryParam("type") String type) {
|
||||
auth.requireView();
|
||||
List<ComponentModel> components = Collections.EMPTY_LIST;
|
||||
|
@ -129,13 +131,15 @@ public class ComponentResource {
|
|||
@GET
|
||||
@Path("{id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public ComponentRepresentation getComponent(@PathParam("id") String id) {
|
||||
auth.requireManage();
|
||||
ComponentModel model = realm.getComponent(id);
|
||||
if (model == null) {
|
||||
throw new NotFoundException("Could not find component");
|
||||
}
|
||||
return ModelToRepresentation.toRepresentation(session, model, false);
|
||||
ComponentRepresentation rep = ModelToRepresentation.toRepresentation(session, model, false);
|
||||
return rep;
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.federation.storage;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -28,6 +30,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.CachedUserModel;
|
||||
import org.keycloak.models.cache.infinispan.UserAdapter;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
@ -40,6 +43,7 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -122,6 +126,126 @@ public class UserStorageTest {
|
|||
loginBadPassword("tbrady");
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetTimeoffset() {
|
||||
Time.setOffset(0);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIDE() throws Exception {
|
||||
Thread.sleep(100000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDailyEviction() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.HOUR, 1);
|
||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||
int min = cal.get(Calendar.MINUTE);
|
||||
|
||||
UserStorageProviderModel model = new UserStorageProviderModel(writableProvider);
|
||||
model.setCachePolicy(UserStorageProviderModel.CachePolicy.EVICT_DAILY);
|
||||
model.setEvictionHour(cal.get(Calendar.HOUR_OF_DAY));
|
||||
model.setEvictionMinute(cal.get(Calendar.MINUTE));
|
||||
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
CachedUserModel thor = (CachedUserModel)session.users().getUserByUsername("thor", realm);
|
||||
long thorTimestamp = thor.getCacheTimestamp();
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
Time.setOffset(60 * 2 * 60); // 2 hours
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
UserModel thor2 = session.users().getUserByUsername("thor", realm);
|
||||
Assert.assertFalse(thor2 instanceof CachedUserModel);
|
||||
model.getConfig().remove("cachePolicy");
|
||||
model.getConfig().remove("evictionHour");
|
||||
model.getConfig().remove("evictionMinute");
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeeklyEviction() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
|
||||
// sets day of the week to 4 days from now
|
||||
cal.add(Calendar.HOUR, 4 * 24);
|
||||
|
||||
UserStorageProviderModel model = new UserStorageProviderModel(writableProvider);
|
||||
model.setCachePolicy(UserStorageProviderModel.CachePolicy.EVICT_WEEKLY);
|
||||
model.setEvictionDay(cal.get(Calendar.DAY_OF_WEEK));
|
||||
model.setEvictionHour(cal.get(Calendar.HOUR_OF_DAY));
|
||||
model.setEvictionMinute(cal.get(Calendar.MINUTE));
|
||||
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
CachedUserModel thor = (CachedUserModel)session.users().getUserByUsername("thor", realm);
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
Time.setOffset(60 * 60 * 24 * 2); // 2 days in future, should be cached still
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
// test still
|
||||
UserModel thor2 = session.users().getUserByUsername("thor", realm);
|
||||
Assert.assertTrue(thor2 instanceof CachedUserModel);
|
||||
keycloakRule.stopSession(session, true);
|
||||
Time.setOffset(Time.getOffset() + 60 * 60 * 24 * 3); // 3 days into future, cache will be invalidated
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
thor2 = session.users().getUserByUsername("thor", realm);
|
||||
Assert.assertFalse(thor2 instanceof CachedUserModel);
|
||||
model.getConfig().remove("cachePolicy");
|
||||
model.getConfig().remove("evictionHour");
|
||||
model.getConfig().remove("evictionMinute");
|
||||
model.getConfig().remove("evictionDay");
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCache() {
|
||||
UserStorageProviderModel model = new UserStorageProviderModel(writableProvider);
|
||||
model.setCachePolicy(UserStorageProviderModel.CachePolicy.NO_CACHE);
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
CachedUserModel thor = (CachedUserModel)session.users().getUserByUsername("thor", realm);
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
// test still
|
||||
UserModel thor2 = session.users().getUserByUsername("thor", realm);
|
||||
Assert.assertFalse(thor2 instanceof CachedUserModel);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
thor2 = session.users().getUserByUsername("thor", realm);
|
||||
Assert.assertFalse(thor2 instanceof CachedUserModel);
|
||||
model.getConfig().remove("cachePolicy");
|
||||
model.getConfig().remove("evictionHour");
|
||||
model.getConfig().remove("evictionMinute");
|
||||
model.getConfig().remove("evictionDay");
|
||||
realm.updateComponent(model);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
thor = (CachedUserModel)session.users().getUserByUsername("thor", realm);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
|
|
@ -1178,4 +1178,31 @@ keystores=Keystores
|
|||
add-keystore=Add Keystore
|
||||
add-keystore.placeholder=Add keystore...
|
||||
view=View
|
||||
active=Active
|
||||
active=Active
|
||||
|
||||
Sunday=Sunday
|
||||
Monday=Monday
|
||||
Tuesday=Tuesday
|
||||
Wednesday=Wednesday
|
||||
Thursday=Thursday
|
||||
Friday=Friday
|
||||
Saturday=Saturday
|
||||
|
||||
user-strage-cache=Cache Settings
|
||||
userStorage.cachePolicy=Cache Policy
|
||||
userStorage.cachePolicy.option.DEFAULT=DEFAULT
|
||||
userStorage.cachePolicy.option.EVICT_WEEKLY=EVICT_WEEKLY
|
||||
userStorage.cachePolicy.option.EVICT_DAILY=EVICT_DAILY
|
||||
userStorage.cachePolicy.option.MAX_LIFESPAN=MAX_LIFESPAN
|
||||
userStorage.cachePolicy.option.NO_CACHE=NO_CACHE
|
||||
userStorage.cachePolicy.tooltip=Cache Policy for this storage provider. 'DEFAULT' is whatever the default settings are for the global user cache. 'EVICT_DAILY' is a time of day every day that the user cache will be invalidated. 'EVICT_WEEKLY' is a day of the week and time the cache will be invalidated. 'MAX-LIFESPAN' is the time in milliseconds that will be the lifespan of a cache entry.
|
||||
userStorage.cachePolicy.evictionDay=Eviction Day
|
||||
userStorage.cachePolicy.evictionDay.tooltip=Day of the week the entry will become invalid on
|
||||
userStorage.cachePolicy.evictionHour=Eviction Hour
|
||||
userStorage.cachePolicy.evictionHour.tooltip=Hour of day the entry will become invalid on.
|
||||
userStorage.cachePolicy.evictionMinute=Eviction Minute
|
||||
userStorage.cachePolicy.evictionMinute.tooltip=Minute of day the entry will become invalid on.
|
||||
userStorage.cachePolicy.maxLifespan=Max Lifespan
|
||||
userStorage.cachePolicy.maxLifespan.tooltip=Max lifespan of a user cache entry in milliseconds.
|
||||
|
||||
|
||||
|
|
|
@ -653,6 +653,9 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
|
|||
if (instance.isUserFederationProvider) {
|
||||
return instance.priority;
|
||||
} else {
|
||||
if (!instance.config['priority']) {
|
||||
console.log('getInstancePriority is undefined');
|
||||
}
|
||||
return instance.config['priority'][0];
|
||||
}
|
||||
}
|
||||
|
@ -740,6 +743,12 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
|
|||
if (providerFactory.metadata.synchronizable) {
|
||||
instance.config['fullSyncPeriod'] = ['-1'];
|
||||
instance.config['changedSyncPeriod'] = ['-1'];
|
||||
instance.config['cachePolicy'] = ['DEFAULT'];
|
||||
instance.config['evictionDay'] = [''];
|
||||
instance.config['evictionHour'] = [''];
|
||||
instance.config['evictionMinute'] = [''];
|
||||
instance.config['maxLifespan'] = [''];
|
||||
|
||||
}
|
||||
if (providerFactory.properties) {
|
||||
|
||||
|
@ -769,6 +778,27 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
|
|||
|
||||
}
|
||||
}
|
||||
if (!instance.config['cachePolicy']) {
|
||||
instance.config['cachePolicy'] = ['DEFAULT'];
|
||||
|
||||
}
|
||||
if (!instance.config['evictionDay']) {
|
||||
instance.config['evictionDay'] = [''];
|
||||
|
||||
}
|
||||
if (!instance.config['evictionHour']) {
|
||||
instance.config['evictionHour'] = [''];
|
||||
|
||||
}
|
||||
if (!instance.config['evictionMinute']) {
|
||||
instance.config['evictionMinute'] = [''];
|
||||
|
||||
}
|
||||
if (!instance.config['maxLifespan']) {
|
||||
instance.config['maxLifespan'] = [''];
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
console.log('Manage instance');
|
||||
console.log(instance.name);
|
||||
|
|
|
@ -68,6 +68,153 @@
|
|||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend><span class="text">{{:: 'user-storage-cache-policy' | translate}}</span></legend>
|
||||
<div class="form-group">
|
||||
<label for="cachePolicy" class="col-md-2 control-label">{{:: 'userStorage.cachePolicy' | translate}}</label>
|
||||
<div class="col-md-2">
|
||||
<div>
|
||||
<select id="cachePolicy" ng-model="instance.config['cachePolicy'][0]" class="form-control">
|
||||
<option value="DEFAULT">{{:: 'userStorage.cachePolicy.option.DEFAULT' | translate}}</option>
|
||||
<option value="EVICT_DAILY">{{:: 'userStorage.cachePolicy.option.EVICT_DAILY' | translate}}</option>
|
||||
<option value="EVICT_WEEKLY">{{:: 'userStorage.cachePolicy.option.EVICT_WEEKLY' | translate}}</option>
|
||||
<option value="MAX_LIFESPAN">{{:: 'userStorage.cachePolicy.option.MAX_LIFESPAN' | translate}}</option>
|
||||
<option value="NO_CACHE">{{:: 'userStorage.cachePolicy.option.NO_CACHE' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'userStorage.cachePolicy.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY'">
|
||||
<label for="evictionDay" class="col-md-2 control-label">{{:: 'userStorage.evictionDay' | translate}}</label>
|
||||
<div class="col-md-2">
|
||||
<div>
|
||||
<select id="evictionDay" ng-model="instance.config['evictionDay'][0]" class="form-control">
|
||||
<option value="1">{{:: 'Sunday' | translate}}</option>
|
||||
<option value="2">{{:: 'Monday' | translate}}</option>
|
||||
<option value="3">{{:: 'Tuesday' | translate}}</option>
|
||||
<option value="4">{{:: 'Wednesday' | translate}}</option>
|
||||
<option value="5">{{:: 'Thursday' | translate}}</option>
|
||||
<option value="6">{{:: 'Friday' | translate}}</option>
|
||||
<option value="7">{{:: 'Saturday' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
|
||||
<label class="col-md-2 control-label" for="evictionHour">{{:: 'userStorage.cachePolicy.evictionHour' | translate}}</label>
|
||||
<div class="col-md-2">
|
||||
<div>
|
||||
<select id="evictionHour" ng-model="instance.config['evictionHour'][0]" class="form-control">
|
||||
<option value="0">00</option>
|
||||
<option value="1">01</option>
|
||||
<option value="2">02</option>
|
||||
<option value="3">03</option>
|
||||
<option value="4">04</option>
|
||||
<option value="5">05</option>
|
||||
<option value="6">06</option>
|
||||
<option value="7">07</option>
|
||||
<option value="8">08</option>
|
||||
<option value="9">09</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
|
||||
<label class="col-md-2 control-label" for="evictionMinute">{{:: 'userStorage.cachePolicy.evictionMinute' | translate}}</label>
|
||||
<div class="col-md-2">
|
||||
<div>
|
||||
<select id="evictionMinute" ng-model="instance.config['evictionMinute'][0]" class="form-control">
|
||||
<option value="0">00</option>
|
||||
<option value="1">01</option>
|
||||
<option value="2">02</option>
|
||||
<option value="3">03</option>
|
||||
<option value="4">04</option>
|
||||
<option value="5">05</option>
|
||||
<option value="6">06</option>
|
||||
<option value="7">07</option>
|
||||
<option value="8">08</option>
|
||||
<option value="9">09</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
<option value="32">32</option>
|
||||
<option value="33">33</option>
|
||||
<option value="34">34</option>
|
||||
<option value="35">35</option>
|
||||
<option value="36">36</option>
|
||||
<option value="37">37</option>
|
||||
<option value="38">38</option>
|
||||
<option value="39">39</option>
|
||||
<option value="40">40</option>
|
||||
<option value="41">41</option>
|
||||
<option value="42">42</option>
|
||||
<option value="43">43</option>
|
||||
<option value="44">44</option>
|
||||
<option value="45">45</option>
|
||||
<option value="46">46</option>
|
||||
<option value="47">47</option>
|
||||
<option value="48">48</option>
|
||||
<option value="49">49</option>
|
||||
<option value="50">50</option>
|
||||
<option value="51">51</option>
|
||||
<option value="52">52</option>
|
||||
<option value="53">53</option>
|
||||
<option value="54">54</option>
|
||||
<option value="55">55</option>
|
||||
<option value="56">56</option>
|
||||
<option value="57">57</option>
|
||||
<option value="58">58</option>
|
||||
<option value="59">59</option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'MAX_LIFESPAN'">
|
||||
<label class="col-md-2 control-label" for="maxLifespan">{{:: 'userStorage.cachePolicy.maxLifespan' | translate}}</label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" type="text" ng-model="instance.config['maxLifespan'][0]" id="maxLifespan" />
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageUsers">
|
||||
<button kc-save>{{:: 'save' | translate}}</button>
|
||||
|
|
Loading…
Reference in a new issue