Merge pull request #4132 from mposolda/cross-dc4-squash

KEYCLOAK-4626 KEYCLOAK-4627 Authentication sessions & Action tokens
This commit is contained in:
Marek Posolda authored and GitHub committed 2017-05-15 12:46:43 +02:00
commit 70d7e07526
324 files changed
+12794 -4243

No files matched your search

@@ -19,6 +19,8 @@ package org.keycloak.connections.infinispan;
import java.util.concurrent.TimeUnit;
import org.infinispan.commons.util.FileLookup;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
@@ -27,12 +29,13 @@ import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.eviction.EvictionType;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.jboss.logging.Logger;
import org.jgroups.JChannel;
import org.keycloak.Config;
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
import org.keycloak.models.KeycloakSession;
@@ -119,7 +122,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
} catch (Exception e) {
@@ -138,7 +143,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
configureTransport(gcb, nodeName);
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
@@ -181,6 +187,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfiguration);
// Retrieve caches to enforce rebalance
cacheManager.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
ConfigurationBuilder replicationConfigBuilder = new ConfigurationBuilder();
if (clustered) {
@@ -219,6 +233,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.KEYS_CACHE_NAME, getKeysCacheConfig());
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
cacheManager.defineConfiguration(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, getActionTokenCacheConfig());
cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
}
private Configuration getRevisionCacheConfig(long maxEntries) {
@@ -269,4 +286,40 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
return cb.build();
}
private Configuration getActionTokenCacheConfig() {
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.eviction()
.strategy(EvictionStrategy.NONE)
.type(EvictionType.COUNT)
.size(InfinispanConnectionProvider.ACTION_TOKEN_CACHE_DEFAULT_MAX);
cb.expiration()
.maxIdle(InfinispanConnectionProvider.ACTION_TOKEN_MAX_IDLE_SECONDS, TimeUnit.SECONDS)
.wakeUpInterval(InfinispanConnectionProvider.ACTION_TOKEN_WAKE_UP_INTERVAL_SECONDS, TimeUnit.SECONDS);
return cb.build();
}
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
if (nodeName == null) {
gcb.transport().defaultTransport();
} else {
FileLookup fileLookup = FileLookupFactory.newInstance();
try {
// Compatibility with Wildfly
JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
channel.setName(nodeName);
JGroupsTransport transport = new JGroupsTransport(channel);
gcb.transport().nodeName(nodeName);
gcb.transport().transport(transport);
logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
@@ -36,13 +36,22 @@ public interface InfinispanConnectionProvider extends Provider {
String SESSION_CACHE_NAME = "sessions";
String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
String AUTHENTICATION_SESSIONS_CACHE_NAME = "authenticationSessions";
String WORK_CACHE_NAME = "work";
String AUTHORIZATION_CACHE_NAME = "authorization";
String ACTION_TOKEN_CACHE = "actionTokens";
int ACTION_TOKEN_CACHE_DEFAULT_MAX = -1;
int ACTION_TOKEN_MAX_IDLE_SECONDS = -1;
long ACTION_TOKEN_WAKE_UP_INTERVAL_SECONDS = 5 * 60 * 1000l;
String KEYS_CACHE_NAME = "keys";
int KEYS_CACHE_DEFAULT_MAX = 1000;
int KEYS_CACHE_MAX_IDLE_SECONDS = 3600;
// System property used on Wildfly to identify distributedCache address and sticky session route
String JBOSS_NODE_NAME = "jboss.node.name";
<K, V> Cache<K, V> getCache(String name);
@@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
/**
* Event requesting adding of an invalidated action token.
*/
public class AddInvalidatedActionTokenEvent implements ClusterEvent {
private final ActionTokenReducedKey key;
private final int expirationInSecs;
private final ActionTokenValueEntity tokenValue;
public AddInvalidatedActionTokenEvent(ActionTokenReducedKey key, int expirationInSecs, ActionTokenValueEntity tokenValue) {
this.key = key;
this.expirationInSecs = expirationInSecs;
this.tokenValue = tokenValue;
}
public ActionTokenReducedKey getKey() {
return key;
}
public int getExpirationInSecs() {
return expirationInSecs;
}
public ActionTokenValueEntity getTokenValue() {
return tokenValue;
}
}
@@ -474,6 +474,30 @@ public class RealmAdapter implements CachedRealmModel {
updated.setAccessCodeLifespanLogin(seconds);
}
@Override
public int getActionTokenGeneratedByAdminLifespan() {
if (isUpdated()) return updated.getActionTokenGeneratedByAdminLifespan();
return cached.getActionTokenGeneratedByAdminLifespan();
}
@Override
public void setActionTokenGeneratedByAdminLifespan(int seconds) {
getDelegateForUpdate();
updated.setActionTokenGeneratedByAdminLifespan(seconds);
}
@Override
public int getActionTokenGeneratedByUserLifespan() {
if (isUpdated()) return updated.getActionTokenGeneratedByUserLifespan();
return cached.getActionTokenGeneratedByUserLifespan();
}
@Override
public void setActionTokenGeneratedByUserLifespan(int seconds) {
getDelegateForUpdate();
updated.setActionTokenGeneratedByUserLifespan(seconds);
}
@Override
public List<RequiredCredentialModel> getRequiredCredentials() {
if (isUpdated()) return updated.getRequiredCredentials();
@@ -0,0 +1,42 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import org.keycloak.cluster.ClusterEvent;
/**
* Event requesting removal of the action tokens with the given user and action regardless of nonce.
*/
public class RemoveActionTokensSpecificEvent implements ClusterEvent {
private final String userId;
private final String actionId;
public RemoveActionTokensSpecificEvent(String userId, String actionId) {
this.userId = userId;
this.actionId = actionId;
}
public String getUserId() {
return userId;
}
public String getActionId() {
return actionId;
}
}
@@ -83,6 +83,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected int accessCodeLifespan;
protected int accessCodeLifespanUserAction;
protected int accessCodeLifespanLogin;
protected int actionTokenGeneratedByAdminLifespan;
protected int actionTokenGeneratedByUserLifespan;
protected int notBefore;
protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy;
@@ -175,6 +177,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
accessCodeLifespan = model.getAccessCodeLifespan();
accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
actionTokenGeneratedByAdminLifespan = model.getActionTokenGeneratedByAdminLifespan();
actionTokenGeneratedByUserLifespan = model.getActionTokenGeneratedByUserLifespan();
notBefore = model.getNotBefore();
passwordPolicy = model.getPasswordPolicy();
otpPolicy = model.getOTPPolicy();
@@ -399,6 +403,14 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return accessCodeLifespanLogin;
}
public int getActionTokenGeneratedByAdminLifespan() {
return actionTokenGeneratedByAdminLifespan;
}
public int getActionTokenGeneratedByUserLifespan() {
return actionTokenGeneratedByUserLifespan;
}
public List<RequiredCredentialModel> getRequiredCredentials() {
return requiredCredentials;
}
@@ -0,0 +1,53 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan.events;
import org.keycloak.cluster.ClusterEvent;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
* @author hmlnarik
*/
public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
private String authSessionId;
private Map<String, String> authNotesFragment;
public static AuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, Map<String, String> authNotesFragment) {
AuthenticationSessionAuthNoteUpdateEvent event = new AuthenticationSessionAuthNoteUpdateEvent();
event.authSessionId = authSessionId;
event.authNotesFragment = new LinkedHashMap<>(authNotesFragment);
return event;
}
public String getAuthSessionId() {
return authSessionId;
}
public Map<String, String> getAuthNotesFragment() {
return authNotesFragment;
}
@Override
public String toString() {
return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, authNotesFragment=%s ]", authSessionId, authNotesFragment);
}
}
@@ -0,0 +1,197 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.infinispan.Cache;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
private final AuthenticatedClientSessionEntity entity;
private final ClientModel client;
private final InfinispanUserSessionProvider provider;
private final Cache<String, SessionEntity> cache;
private UserSessionAdapter userSession;
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
this.provider = provider;
this.entity = entity;
this.client = client;
this.cache = cache;
this.userSession = userSession;
}
private void update() {
provider.getTx().replace(cache, userSession.getEntity().getId(), userSession.getEntity());
}
@Override
public void setUserSession(UserSessionModel userSession) {
String clientUUID = client.getId();
UserSessionEntity sessionEntity = this.userSession.getEntity();
// Dettach userSession
if (userSession == null) {
if (sessionEntity.getAuthenticatedClientSessions() != null) {
sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
update();
this.userSession = null;
}
} else {
this.userSession = (UserSessionAdapter) userSession;
if (sessionEntity.getAuthenticatedClientSessions() == null) {
sessionEntity.setAuthenticatedClientSessions(new HashMap<>());
}
sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
update();
}
}
@Override
public UserSessionModel getUserSession() {
return this.userSession;
}
@Override
public String getRedirectUri() {
return entity.getRedirectUri();
}
@Override
public void setRedirectUri(String uri) {
entity.setRedirectUri(uri);
update();
}
@Override
public String getId() {
return null;
}
@Override
public RealmModel getRealm() {
return userSession.getRealm();
}
@Override
public ClientModel getClient() {
return client;
}
@Override
public int getTimestamp() {
return entity.getTimestamp();
}
@Override
public void setTimestamp(int timestamp) {
entity.setTimestamp(timestamp);
update();
}
@Override
public String getAction() {
return entity.getAction();
}
@Override
public void setAction(String action) {
entity.setAction(action);
update();
}
@Override
public String getProtocol() {
return entity.getAuthMethod();
}
@Override
public void setProtocol(String method) {
entity.setAuthMethod(method);
update();
}
@Override
public Set<String> getRoles() {
return entity.getRoles();
}
@Override
public void setRoles(Set<String> roles) {
entity.setRoles(roles);
update();
}
@Override
public Set<String> getProtocolMappers() {
return entity.getProtocolMappers();
}
@Override
public void setProtocolMappers(Set<String> protocolMappers) {
entity.setProtocolMappers(protocolMappers);
update();
}
@Override
public String getNote(String name) {
return entity.getNotes()==null ? null : entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<>());
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
}
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
}
@@ -17,44 +17,48 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.infinispan.Cache;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* NOTE: Calling setter doesn't automatically enlist for update
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionAdapter implements ClientSessionModel {
public class AuthenticationSessionAdapter implements AuthenticationSessionModel {
private KeycloakSession session;
private InfinispanUserSessionProvider provider;
private Cache<String, SessionEntity> cache;
private InfinispanAuthenticationSessionProvider provider;
private Cache<String, AuthenticationSessionEntity> cache;
private RealmModel realm;
private ClientSessionEntity entity;
private boolean offline;
private AuthenticationSessionEntity entity;
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm,
ClientSessionEntity entity, boolean offline) {
public AuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider, Cache<String, AuthenticationSessionEntity> cache, RealmModel realm,
AuthenticationSessionEntity entity) {
this.session = session;
this.provider = provider;
this.cache = cache;
this.realm = realm;
this.entity = entity;
this.offline = offline;
}
void update() {
provider.tx.replace(cache, entity.getId(), entity);
}
@Override
public String getId() {
return entity.getId();
@@ -67,36 +71,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public ClientModel getClient() {
return realm.getClientById(entity.getClient());
}
@Override
public UserSessionAdapter getUserSession() {
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
}
@Override
public void setUserSession(UserSessionModel userSession) {
if (userSession == null) {
if (entity.getUserSession() != null) {
provider.dettachSession(getUserSession(), this);
}
entity.setUserSession(null);
} else {
UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
if (entity.getUserSession() != null) {
if (entity.getUserSession().equals(userSession.getId())) {
return;
} else {
provider.dettachSession(userSessionAdapter, this);
}
} else {
provider.attachSession(userSessionAdapter, this);
}
entity.setUserSession(userSession.getId());
}
update();
return realm.getClientById(entity.getClientUuid());
}
@Override
@@ -157,52 +132,104 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
public String getProtocol() {
return entity.getProtocol();
}
@Override
public void setAuthMethod(String authMethod) {
entity.setAuthMethod(authMethod);
public void setProtocol(String protocol) {
entity.setProtocol(protocol);
update();
}
@Override
public String getNote(String name) {
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
public String getClientNote(String name) {
return (entity.getClientNotes() != null && name != null) ? entity.getClientNotes().get(name) : null;
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
public void setClientNote(String name, String value) {
if (entity.getClientNotes() == null) {
entity.setClientNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getClientNotes().remove(name);
} else {
entity.getClientNotes().put(name, value);
}
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
public void removeClientNote(String name) {
if (entity.getClientNotes() != null && name != null) {
entity.getClientNotes().remove(name);
}
update();
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
public Map<String, String> getClientNotes() {
if (entity.getClientNotes() == null || entity.getClientNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new ConcurrentHashMap<>();
copy.putAll(entity.getClientNotes());
return copy;
}
@Override
public void clearClientNotes() {
entity.setClientNotes(new ConcurrentHashMap<>());
update();
}
@Override
public String getAuthNote(String name) {
return (entity.getAuthNotes() != null && name != null) ? entity.getAuthNotes().get(name) : null;
}
@Override
public void setAuthNote(String name, String value) {
if (entity.getAuthNotes() == null) {
entity.setAuthNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getAuthNotes().remove(name);
} else {
entity.getAuthNotes().put(name, value);
}
}
update();
}
@Override
public void removeAuthNote(String name) {
if (entity.getAuthNotes() != null && name != null) {
entity.getAuthNotes().remove(name);
}
update();
}
@Override
public void clearAuthNotes() {
entity.setAuthNotes(new ConcurrentHashMap<>());
update();
}
@Override
public void setUserSessionNote(String name, String value) {
if (entity.getUserSessionNotes() == null) {
entity.setUserSessionNotes(new HashMap<String, String>());
entity.setUserSessionNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getUserSessionNotes().remove(name);
} else {
entity.getUserSessionNotes().put(name, value);
}
}
entity.getUserSessionNotes().put(name, value);
update();
}
@@ -212,14 +239,14 @@ public class ClientSessionAdapter implements ClientSessionModel {
if (entity.getUserSessionNotes() == null) {
return Collections.EMPTY_MAP;
}
HashMap<String, String> copy = new HashMap<>();
ConcurrentHashMap<String, String> copy = new ConcurrentHashMap<>();
copy.putAll(entity.getUserSessionNotes());
return copy;
}
@Override
public void clearUserSessionNotes() {
entity.setUserSessionNotes(new HashMap<String, String>());
entity.setUserSessionNotes(new ConcurrentHashMap<>());
update();
}
@@ -248,7 +275,6 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public void addRequiredAction(UserModel.RequiredAction action) {
addRequiredAction(action.name());
}
@Override
@@ -256,24 +282,22 @@ public class ClientSessionAdapter implements ClientSessionModel {
removeRequiredAction(action.name());
}
void update() {
provider.getTx().replace(cache, entity.getId(), entity);
}
@Override
public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus();
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatus() {
return entity.getExecutionStatus();
}
@Override
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
public void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status) {
entity.getExecutionStatus().put(authenticator, status);
update();
}
@Override
public void clearExecutionStatus() {
entity.getAuthenticatorStatus().clear();
entity.getExecutionStatus().clear();
update();
}
@@ -286,7 +310,22 @@ public class ClientSessionAdapter implements ClientSessionModel {
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
update();
}
@Override
public void updateClient(ClientModel client) {
entity.setClientUuid(client.getId());
update();
}
@Override
public void restartSession(RealmModel realm, ClientModel client) {
String id = entity.getId();
entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setClientUuid(client.getId());
entity.setTimestamp(Time.currentTime());
update();
}
}
@@ -19,11 +19,9 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -41,21 +39,6 @@ public class Consumers {
return new UserSessionModelsConsumer(provider, realm, offline);
}
public static class UserSessionIdAndTimestampConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private Map<String, Integer> sessions = new HashMap<>();
@Override
public void accept(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (e instanceof ClientSessionEntity) {
ClientSessionEntity ce = (ClientSessionEntity) e;
sessions.put(ce.getUserSession(), ce.getTimestamp());
}
}
}
public static class UserSessionModelsConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private InfinispanUserSessionProvider provider;
@@ -0,0 +1,98 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
import org.infinispan.Cache;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvider {
private final Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache;
private final InfinispanKeycloakTransaction tx;
private final KeycloakSession session;
public InfinispanActionTokenStoreProvider(KeycloakSession session, Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache) {
this.session = session;
this.actionKeyCache = actionKeyCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public void close() {
}
@Override
public void put(ActionTokenKeyModel key, Map<String, String> notes) {
if (key == null || key.getUserId() == null || key.getActionId() == null) {
return;
}
ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce());
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
}
@Override
public ActionTokenValueModel get(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
return this.actionKeyCache.getAdvancedCache().get(key);
}
@Override
public ActionTokenValueModel remove(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
ActionTokenValueEntity value = this.actionKeyCache.get(key);
if (value != null) {
this.tx.remove(actionKeyCache, key);
}
return value;
}
public void removeAll(String userId, String actionId) {
if (userId == null || actionId == null) {
return;
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
}
}
@@ -0,0 +1,100 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
/**
* If expiration is set to this value, no expiration is set on the corresponding cache entry (hence cache default is honored)
*/
private static final int DEFAULT_CACHE_EXPIRATION = 0;
private Config.Scope config;
@Override
public ActionTokenStoreProvider create(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
if (event instanceof RemoveActionTokensSpecificEvent) {
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
actionTokenCache
.getAdvancedCache()
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
.keySet()
.stream()
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
.forEach(actionTokenCache::remove);
} else if (event instanceof AddInvalidatedActionTokenEvent) {
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
actionTokenCache.put(e.getKey(), e.getTokenValue());
} else {
actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
}
}
});
return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
}
@Override
public void init(Scope config) {
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}
@@ -0,0 +1,162 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import java.util.Iterator;
import java.util.Map;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.models.sessions.infinispan.stream.AuthenticationSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.AuthenticationSessionProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanAuthenticationSessionProvider implements AuthenticationSessionProvider {
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProvider.class);
private final KeycloakSession session;
private final Cache<String, AuthenticationSessionEntity> cache;
protected final InfinispanKeycloakTransaction tx;
public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<String, AuthenticationSessionEntity> cache) {
this.session = session;
this.cache = cache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
return createAuthenticationSession(id, realm, client);
}
@Override
public AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client) {
AuthenticationSessionEntity entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClientUuid(client.getId());
tx.put(cache, id, entity);
AuthenticationSessionAdapter wrap = wrap(realm, entity);
return wrap;
}
private AuthenticationSessionAdapter wrap(RealmModel realm, AuthenticationSessionEntity entity) {
return entity==null ? null : new AuthenticationSessionAdapter(session, this, cache, realm, entity);
}
@Override
public AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId) {
AuthenticationSessionEntity entity = getAuthenticationSessionEntity(realm, authenticationSessionId);
return wrap(realm, entity);
}
private AuthenticationSessionEntity getAuthenticationSessionEntity(RealmModel realm, String authSessionId) {
// Chance created in this transaction
AuthenticationSessionEntity entity = tx.get(cache, authSessionId);
if (entity == null) {
entity = cache.get(authSessionId);
}
return entity;
}
@Override
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession) {
tx.remove(cache, authenticationSession.getId());
}
@Override
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).expired(expired)).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
AuthenticationSessionEntity entity = itr.next().getValue();
tx.remove(cache, entity.getId());
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
// TODO: Should likely listen to "RealmRemovedEvent" received from cluster and clean just local sessions
@Override
public void onRealmRemoved(RealmModel realm) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId())).iterator();
while (itr.hasNext()) {
cache.remove(itr.next().getKey());
}
}
// TODO: Should likely listen to "ClientRemovedEvent" received from cluster and clean just local sessions
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
while (itr.hasNext()) {
cache.remove(itr.next().getKey());
}
}
@Override
public void updateNonlocalSessionAuthNotes(String authSessionId, Map<String, String> authNotesFragment) {
if (authSessionId == null) {
return;
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.notify(
InfinispanAuthenticationSessionProviderFactory.AUTHENTICATION_SESSION_EVENTS,
AuthenticationSessionAuthNoteUpdateEvent.create(authSessionId, authNotesFragment),
true
);
}
@Override
public void close() {
}
}
@@ -0,0 +1,113 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.Config;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanAuthenticationSessionProviderFactory implements AuthenticationSessionProviderFactory {
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProviderFactory.class);
private volatile Cache<String, AuthenticationSessionEntity> authSessionsCache;
public static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS";
@Override
public void init(Config.Scope config) {
}
@Override
public AuthenticationSessionProvider create(KeycloakSession session) {
lazyInit(session);
return new InfinispanAuthenticationSessionProvider(session, authSessionsCache);
}
private void updateAuthNotes(ClusterEvent clEvent) {
if (! (clEvent instanceof AuthenticationSessionAuthNoteUpdateEvent)) {
return;
}
AuthenticationSessionAuthNoteUpdateEvent event = (AuthenticationSessionAuthNoteUpdateEvent) clEvent;
AuthenticationSessionEntity authSession = this.authSessionsCache.get(event.getAuthSessionId());
updateAuthSession(authSession, event.getAuthNotesFragment());
}
private static void updateAuthSession(AuthenticationSessionEntity authSession, Map<String, String> authNotesFragment) {
if (authSession != null) {
if (authSession.getAuthNotes() == null) {
authSession.setAuthNotes(new ConcurrentHashMap<>());
}
for (Entry<String, String> me : authNotesFragment.entrySet()) {
String value = me.getValue();
if (value == null) {
authSession.getAuthNotes().remove(me.getKey());
} else {
authSession.getAuthNotes().put(me.getKey(), value);
}
}
}
}
private void lazyInit(KeycloakSession session) {
if (authSessionsCache == null) {
synchronized (this) {
if (authSessionsCache == null) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
authSessionsCache = connections.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
log.debug("Registered cluster listeners");
}
}
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}
@@ -0,0 +1,218 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.infinispan.context.Flag;
import org.keycloak.models.KeycloakTransaction;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class InfinispanKeycloakTransaction implements KeycloakTransaction {
private final static Logger log = Logger.getLogger(InfinispanKeycloakTransaction.class);
public enum CacheOperation {
ADD, ADD_WITH_LIFESPAN, REMOVE, REPLACE, ADD_IF_ABSENT // ADD_IF_ABSENT throws an exception if there is existing value
}
private boolean active;
private boolean rollback;
private final Map<Object, CacheTask> tasks = new LinkedHashMap<>();
@Override
public void begin() {
active = true;
}
@Override
public void commit() {
if (rollback) {
throw new RuntimeException("Rollback only!");
}
tasks.values().forEach(CacheTask::execute);
}
@Override
public void rollback() {
tasks.clear();
}
@Override
public void setRollbackOnly() {
rollback = true;
}
@Override
public boolean getRollbackOnly() {
return rollback;
}
@Override
public boolean isActive() {
return active;
}
public <K, V> void put(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).put(key, value);
}
});
}
}
public <K, V> void put(Cache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_WITH_LIFESPAN, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).put(key, value, lifespan, lifespanUnit);
}
});
}
}
public <K, V> void putIfAbsent(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_IF_ABSENT, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
V existing = cache.putIfAbsent(key, value);
if (existing != null) {
throw new IllegalStateException("There is already existing value in cache for key " + key);
}
}
});
}
}
public <K, V> void replace(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
if (current instanceof CacheTaskWithValue) {
((CacheTaskWithValue<V>) current).setValue(value);
}
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).replace(key, value);
}
});
}
}
public <K, V> void notify(ClusterProvider clusterProvider, String taskKey, ClusterEvent event, boolean ignoreSender) {
log.tracev("Adding cache operation SEND_EVENT: {0}", event);
String theTaskKey = taskKey;
int i = 1;
while (tasks.containsKey(theTaskKey)) {
theTaskKey = taskKey + "-" + (i++);
}
tasks.put(taskKey, () -> clusterProvider.notify(taskKey, event, ignoreSender));
}
public <K, V> void remove(Cache<K, V> cache, K key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, key);
tasks.put(taskKey, () -> decorateCache(cache).remove(key));
}
// This is for possibility to lookup for session by id, which was created in this transaction
public <K, V> V get(Cache<K, V> cache, K key) {
Object taskKey = getTaskKey(cache, key);
CacheTask<V> current = tasks.get(taskKey);
if (current != null) {
if (current instanceof CacheTaskWithValue) {
return ((CacheTaskWithValue<V>) current).getValue();
}
return null;
}
// Should we have per-transaction cache for lookups?
return cache.get(key);
}
private static <K, V> Object getTaskKey(Cache<K, V> cache, K key) {
if (key instanceof String) {
return new StringBuilder(cache.getName())
.append("::")
.append(key).toString();
} else {
return key;
}
}
public interface CacheTask<V> {
void execute();
}
public abstract class CacheTaskWithValue<V> implements CacheTask<V> {
protected V value;
public CacheTaskWithValue(V value) {
this.value = value;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
// Ignore return values. Should have better performance within cluster / cross-dc env
private static <K, V> Cache<K, V> decorateCache(Cache<K, V> cache) {
return cache.getAdvancedCache()
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP);
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.remoting.transport.Address;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.sessions.StickySessionEncoderProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanStickySessionEncoderProvider implements StickySessionEncoderProvider {
private final KeycloakSession session;
private final String myNodeName;
public InfinispanStickySessionEncoderProvider(KeycloakSession session, String myNodeName) {
this.session = session;
this.myNodeName = myNodeName;
}
@Override
public String encodeSessionId(String sessionId) {
String nodeName = getNodeName(sessionId);
if (nodeName != null) {
return sessionId + '.' + nodeName;
} else {
return sessionId;
}
}
@Override
public String decodeSessionId(String encodedSessionId) {
int index = encodedSessionId.indexOf('.');
return index == -1 ? encodedSessionId : encodedSessionId.substring(0, index);
}
@Override
public void close() {
}
private String getNodeName(String sessionId) {
InfinispanConnectionProvider ispnProvider = session.getProvider(InfinispanConnectionProvider.class);
Cache cache = ispnProvider.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME);
DistributionManager distManager = cache.getAdvancedCache().getDistributionManager();
if (distManager != null) {
// Sticky session to the node, who owns this authenticationSession
Address address = distManager.getPrimaryLocation(sessionId);
return address.toString();
} else {
// Fallback to jbossNodeName if authSession cache is local
return myNodeName;
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.sessions.StickySessionEncoderProvider;
import org.keycloak.sessions.StickySessionEncoderProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanStickySessionEncoderProviderFactory implements StickySessionEncoderProviderFactory {
private String myNodeName;
@Override
public StickySessionEncoderProvider create(KeycloakSession session) {
return new InfinispanStickySessionEncoderProvider(session, myNodeName);
}
@Override
public void init(Config.Scope config) {
myNodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}
@@ -23,10 +23,9 @@ import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
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.UserLoginFailureModel;
import org.keycloak.models.UserModel;
@@ -34,31 +33,29 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
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.RealmInfoUtil;
import java.util.Collection;
import java.util.Collections;
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.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -90,28 +87,27 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClient(client.getId());
tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false);
return wrap;
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, sessionCache);
adapter.setUserSession(userSession);
return adapter;
}
@Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
String id = KeycloakModelUtils.generateId();
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(id);
updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
tx.putIfAbsent(sessionCache, id, entity);
return wrap(realm, entity, false);
}
void updateSessionEntity(UserSessionEntity entity, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
entity.setRealm(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
@@ -126,42 +122,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
tx.put(sessionCache, id, entity);
return wrap(realm, entity, false);
}
@Override
public ClientSessionModel getClientSession(RealmModel realm, String id) {
return getClientSession(realm, id, false);
}
protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
ClientSessionEntity entity = (ClientSessionEntity) cache.get(id);
// Chance created in this transaction
if (entity == null) {
entity = (ClientSessionEntity) tx.get(cache, id);
}
return wrap(realm, entity, offline);
}
@Override
public ClientSessionModel getClientSession(String id) {
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
// Chance created in this transaction
if (entity == null) {
entity = (ClientSessionEntity) tx.get(sessionCache, id);
}
if (entity != null) {
RealmModel realm = session.realms().getRealm(entity.getRealm());
return wrap(realm, entity, false);
}
return null;
}
@Override
@@ -171,11 +132,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
UserSessionEntity entity = (UserSessionEntity) cache.get(id);
UserSessionEntity entity = (UserSessionEntity) tx.get(cache, id); // Chance created in this transaction
// Chance created in this transaction
if (entity == null) {
entity = (UserSessionEntity) tx.get(cache, id);
entity = (UserSessionEntity) cache.get(id);
}
return wrap(realm, entity, offline);
@@ -221,37 +181,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
final Cache<String, SessionEntity> cache = getCache(offline);
Iterator<UserSessionTimestamp> itr = cache.entrySet().stream()
.filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession())
.map(Mappers.clientSessionToUserSessionTimestamp())
.iterator();
Stream<UserSessionEntity> stream = cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.map(Mappers.userSessionEntity())
.sorted(Comparators.userSessionLastSessionRefresh());
Map<String, UserSessionTimestamp> m = new HashMap<>();
while(itr.hasNext()) {
UserSessionTimestamp next = itr.next();
if (!m.containsKey(next.getUserSessionId()) || m.get(next.getUserSessionId()).getClientSessionTimestamp() < next.getClientSessionTimestamp()) {
m.put(next.getUserSessionId(), next);
}
// Doesn't work due to ISPN-6575 . TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0
// if (firstResult > 0) {
// stream = stream.skip(firstResult);
// }
//
// if (maxResults > 0) {
// stream = stream.limit(maxResults);
// }
//
// List<UserSessionEntity> entities = stream.collect(Collectors.toList());
// Workaround for ISPN-6575 TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0 and replace with the more effective code above
if (firstResult < 0) {
firstResult = 0;
}
if (maxResults < 0) {
maxResults = Integer.MAX_VALUE;
}
Stream<UserSessionTimestamp> stream = new LinkedList<>(m.values()).stream().sorted(Comparators.userSessionTimestamp());
int count = firstResult + maxResults;
if (count > 0) {
stream = stream.limit(count);
}
List<UserSessionEntity> entities = stream.collect(Collectors.toList());
if (firstResult > 0) {
stream = stream.skip(firstResult);
if (firstResult > entities.size()) {
return Collections.emptyList();
}
if (maxResults > 0) {
stream = stream.limit(maxResults);
}
maxResults = Math.min(maxResults, entities.size() - firstResult);
entities = entities.subList(firstResult, firstResult + maxResults);
final List<UserSessionModel> sessions = new LinkedList<>();
stream.forEach(new Consumer<UserSessionTimestamp>() {
entities.stream().forEach(new Consumer<UserSessionEntity>() {
@Override
public void accept(UserSessionTimestamp userSessionTimestamp) {
SessionEntity entity = cache.get(userSessionTimestamp.getUserSessionId());
if (entity != null) {
sessions.add(wrap(realm, (UserSessionEntity) entity, offline));
}
public void accept(UserSessionEntity userSessionEntity) {
sessions.add(wrap(realm, userSessionEntity, offline));
}
});
@@ -264,7 +237,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
return getCache(offline).entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession()).map(Mappers.clientSessionToUserSessionId()).distinct().count();
return getCache(offline).entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.count();
}
@Override
@@ -294,9 +269,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
removeExpiredUserSessions(realm);
removeExpiredClientSessions(realm);
removeExpiredOfflineUserSessions(realm);
removeExpiredOfflineClientSessions(realm);
removeExpiredClientInitialAccess(realm);
}
@@ -313,33 +286,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
counter++;
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
tx.remove(sessionCache, entity.getId());
if (entity.getClientSessions() != null) {
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(sessionCache, clientSessionId);
}
}
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientSessions(RealmModel realm) {
int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
tx.remove(sessionCache, itr.next().getKey());
}
log.debugf("Removed %d expired client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineUserSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
@@ -357,33 +308,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
persister.removeUserSession(entity.getId(), true);
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(offlineSessionCache, clientSessionId);
for (String clientUUID : entity.getAuthenticatedClientSessions().keySet()) {
persister.removeClientSession(entity.getId(), clientUUID, true);
}
}
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineClientSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<String> itr = offlineSessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredOffline)).map(Mappers.sessionId()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
String sessionId = itr.next();
tx.remove(offlineSessionCache, sessionId);
persister.removeClientSession(sessionId, true);
}
log.debugf("Removed %d expired offline client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientInitialAccess(RealmModel realm) {
Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
@@ -445,21 +377,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
onClientRemoved(realm, client, true);
onClientRemoved(realm, client, false);
}
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
Iterator<Map.Entry<String, SessionEntity>> itr = cache.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
while (itr.hasNext()) {
ClientSessionEntity entity = (ClientSessionEntity) itr.next().getValue();
ClientSessionAdapter adapter = wrap(realm, entity, offline);
adapter.setUserSession(null);
tx.remove(cache, entity.getId());
}
// Nothing for now. userSession.getAuthenticatedClientSessions() will check lazily if particular client exists and update userSession on-the-fly.
}
@@ -475,55 +393,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void close() {
}
void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (!entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().add(clientSessionId);
userSession.update();
}
}
@Override
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
removeClientSession(realm, clientSession, false);
}
protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
if (entity.getClientSessions() != null) {
entity.getClientSessions().remove(clientSession.getId());
}
tx.replace(cache, entity.getId(), entity);
}
tx.remove(cache, clientSession.getId());
}
void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().remove(clientSessionId);
userSession.update();
}
}
protected void removeUserSession(RealmModel realm, UserSessionEntity sessionEntity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
tx.remove(cache, sessionEntity.getId());
if (sessionEntity.getClientSessions() != null) {
for (String clientSessionId : sessionEntity.getClientSessions()) {
tx.remove(cache, clientSessionId);
}
}
}
InfinispanKeycloakTransaction getTx() {
@@ -551,11 +424,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
Cache<String, SessionEntity> cache = getCache(false);
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
@@ -565,14 +433,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
List<ClientSessionModel> models = new LinkedList<>();
for (ClientSessionEntity e : entities) {
models.add(wrap(realm, e, offline));
}
return models;
}
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
if (userSession instanceof UserSessionAdapter) {
return ((UserSessionAdapter) userSession).getEntity();
@@ -585,7 +445,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
UserSessionAdapter offlineUserSession = importUserSession(userSession, true, false);
// started and lastSessionRefresh set to current time
int currentTime = Time.currentTime();
@@ -596,7 +456,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
public UserSessionAdapter getOfflineUserSession(RealmModel realm, String userSessionId) {
return getUserSession(realm, userSessionId, true);
}
@@ -608,9 +468,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
}
@Override
public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession);
// update timestamp to current time
offlineClientSession.setTimestamp(Time.currentTime());
@@ -619,38 +484,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
return getClientSession(realm, clientSessionId, true);
}
@Override
public List<ClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
public List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
Iterator<Map.Entry<String, SessionEntity>> itr = offlineSessionCache.entrySet().stream().filter(UserSessionPredicate.create(realm.getId()).user(user.getId())).iterator();
List<ClientSessionModel> clientSessions = new LinkedList<>();
List<UserSessionModel> userSessions = new LinkedList<>();
while(itr.hasNext()) {
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
Set<String> currClientSessions = entity.getClientSessions();
if (currClientSessions == null) {
continue;
}
for (String clientSessionId : currClientSessions) {
ClientSessionEntity cls = (ClientSessionEntity) offlineSessionCache.get(clientSessionId);
if (cls != null) {
clientSessions.add(wrap(realm, cls, true));
}
}
UserSessionModel userSession = wrap(realm, entity, true);
userSessions.add(userSession);
}
return clientSessions;
}
@Override
public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
removeClientSession(realm, clientSession, true);
return userSessions;
}
@Override
@@ -664,7 +508,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(userSession.getId());
entity.setRealm(userSession.getRealm().getId());
@@ -682,34 +526,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(userSession.getStarted());
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, userSession.getId(), entity);
return wrap(userSession.getRealm(), entity, offline);
UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
// Handle client sessions
if (importAuthenticatedClientSessions) {
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
importClientSession(importedSession, clientSession);
}
}
return importedSession;
}
@Override
public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(clientSession.getId());
entity.setRealm(clientSession.getRealm().getId());
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setAction(clientSession.getAction());
entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
entity.setAuthMethod(clientSession.getAuthMethod());
if (clientSession.getAuthenticatedUser() != null) {
entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
}
entity.setClient(clientSession.getClient().getId());
entity.setAuthMethod(clientSession.getProtocol());
entity.setNotes(clientSession.getNotes());
entity.setProtocolMappers(clientSession.getProtocolMappers());
entity.setRedirectUri(clientSession.getRedirectUri());
entity.setRoles(clientSession.getRoles());
entity.setTimestamp(clientSession.getTimestamp());
entity.setUserSessionNotes(clientSession.getUserSessionNotes());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, clientSession.getId(), entity);
return wrap(clientSession.getRealm(), entity, offline);
Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions();
if (clientSessions == null) {
clientSessions = new HashMap<>();
importedUserSession.getEntity().setAuthenticatedClientSessions(clientSessions);
}
clientSessions.put(clientSession.getClient().getId(), entity);
importedUserSession.update();
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
}
@Override
@@ -732,11 +587,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
Cache<String, SessionEntity> cache = getCache(false);
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
// If created in this transaction
if (entity == null) {
entity = (ClientInitialAccessEntity) tx.get(cache, id);
entity = (ClientInitialAccessEntity) cache.get(id);
}
return wrap(realm, entity);
@@ -757,145 +611,4 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return list;
}
class InfinispanKeycloakTransaction implements KeycloakTransaction {
private boolean active;
private boolean rollback;
private Map<Object, CacheTask> tasks = new HashMap<>();
@Override
public void begin() {
active = true;
}
@Override
public void commit() {
if (rollback) {
throw new RuntimeException("Rollback only!");
}
for (CacheTask task : tasks.values()) {
task.execute();
}
}
@Override
public void rollback() {
tasks.clear();
}
@Override
public void setRollbackOnly() {
rollback = true;
}
@Override
public boolean getRollbackOnly() {
return rollback;
}
@Override
public boolean isActive() {
return active;
}
public void put(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTask(cache, CacheOperation.ADD, key, value));
}
}
public void replace(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
switch (current.operation) {
case ADD:
case REPLACE:
current.value = value;
return;
case REMOVE:
return;
}
} else {
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REPLACE, key, value));
}
}
public void remove(Cache cache, Object key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, key);
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REMOVE, key, null));
}
// This is for possibility to lookup for session by id, which was created in this transaction
public Object get(Cache cache, Object key) {
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
switch (current.operation) {
case ADD:
case REPLACE:
return current.value; }
}
return null;
}
private Object getTaskKey(Cache cache, Object key) {
if (key instanceof String) {
return new StringBuilder(cache.getName())
.append("::")
.append(key.toString()).toString();
} else {
// loginFailure cache
return key;
}
}
public class CacheTask {
private Cache cache;
private CacheOperation operation;
private Object key;
private Object value;
public CacheTask(Cache cache, CacheOperation operation, Object key, Object value) {
this.cache = cache;
this.operation = operation;
this.key = key;
this.value = value;
}
public void execute() {
log.tracev("Executing cache operation: {0} on {1}", operation, key);
switch (operation) {
case ADD:
cache.put(key, value);
break;
case REMOVE:
cache.remove(key);
break;
case REPLACE:
cache.replace(key, value);
break;
}
}
}
}
public enum CacheOperation {
ADD, REMOVE, REPLACE
}
}
@@ -18,11 +18,13 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@@ -60,6 +62,36 @@ public class UserSessionAdapter implements UserSessionModel {
this.offline = offline;
}
@Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions();
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
List<String> removedClientUUIDS = new LinkedList<>();
if (clientSessionEntities != null) {
clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> {
// Check if client still exists
ClientModel client = realm.getClientById(key);
if (client != null) {
result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, cache));
} else {
removedClientUUIDS.add(key);
}
});
}
// Update user session
if (!removedClientUUIDS.isEmpty()) {
for (String clientUUID : removedClientUUIDS) {
entity.getAuthenticatedClientSessions().remove(clientUUID);
}
update();
}
return Collections.unmodifiableMap(result);
}
public String getId() {
return entity.getId();
}
@@ -82,6 +114,12 @@ public class UserSessionAdapter implements UserSessionModel {
return session.users().getUserById(entity.getUser(), realm);
}
@Override
public void setUser(UserModel user) {
entity.setUser(user.getId());
update();
}
@Override
public String getLoginUsername() {
return entity.getLoginUsername();
@@ -159,19 +197,14 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
public List<ClientSessionModel> getClientSessions() {
if (entity.getClientSessions() != null) {
List<ClientSessionModel> clientSessions = new LinkedList<>();
for (String c : entity.getClientSessions()) {
ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
if (clientSession != null) {
clientSessions.add(clientSession);
}
}
return clientSessions;
} else {
return Collections.emptyList();
}
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
provider.updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
entity.setState(null);
entity.setNotes(null);
entity.setAuthenticatedClientSessions(null);
update();
}
@Override
@@ -196,4 +229,7 @@ public class UserSessionAdapter implements UserSessionModel {
provider.getTx().replace(cache, entity.getId(), entity);
}
Cache<String, SessionEntity> getCache() {
return cache;
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import java.io.*;
import java.util.Objects;
import java.util.UUID;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
*
* @author hmlnarik
*/
@SerializeWith(value = ActionTokenReducedKey.ExternalizerImpl.class)
public class ActionTokenReducedKey implements Serializable {
private final String userId;
private final String actionId;
/**
* Nonce that must match.
*/
private final UUID actionVerificationNonce;
public ActionTokenReducedKey(String userId, String actionId, UUID actionVerificationNonce) {
this.userId = userId;
this.actionId = actionId;
this.actionVerificationNonce = actionVerificationNonce;
}
public String getUserId() {
return userId;
}
public String getActionId() {
return actionId;
}
public UUID getActionVerificationNonce() {
return actionVerificationNonce;
}
@Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + Objects.hashCode(this.userId);
hash = 71 * hash + Objects.hashCode(this.actionId);
hash = 71 * hash + Objects.hashCode(this.actionVerificationNonce);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ActionTokenReducedKey other = (ActionTokenReducedKey) obj;
return Objects.equals(this.userId, other.getUserId())
&& Objects.equals(this.actionId, other.getActionId())
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
}
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
@Override
public void writeObject(ObjectOutput output, ActionTokenReducedKey t) throws IOException {
output.writeUTF(t.userId);
output.writeUTF(t.actionId);
output.writeLong(t.actionVerificationNonce.getMostSignificantBits());
output.writeLong(t.actionVerificationNonce.getLeastSignificantBits());
}
@Override
public ActionTokenReducedKey readObject(ObjectInput input) throws IOException, ClassNotFoundException {
return new ActionTokenReducedKey(
input.readUTF(),
input.readUTF(),
new UUID(input.readLong(), input.readLong())
);
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ActionTokenValueModel;
import java.io.*;
import java.util.*;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
* @author hmlnarik
*/
@SerializeWith(ActionTokenValueEntity.ExternalizerImpl.class)
public class ActionTokenValueEntity implements ActionTokenValueModel {
private final Map<String, String> notes;
public ActionTokenValueEntity(Map<String, String> notes) {
this.notes = notes == null ? Collections.EMPTY_MAP : new HashMap<>(notes);
}
@Override
public Map<String, String> getNotes() {
return Collections.unmodifiableMap(notes);
}
@Override
public String getNote(String name) {
return notes.get(name);
}
public static class ExternalizerImpl implements Externalizer<ActionTokenValueEntity> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
output.writeByte(VERSION_1);
output.writeBoolean(! t.notes.isEmpty());
if (! t.notes.isEmpty()) {
output.writeObject(t.notes);
}
}
@Override
public ActionTokenValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
byte version = input.readByte();
if (version != VERSION_1) {
throw new IOException("Invalid version: " + version);
}
boolean notesEmpty = input.readBoolean();
Map<String, String> notes = notesEmpty ? Collections.EMPTY_MAP : (Map<String, String>) input.readObject();
return new ActionTokenValueEntity(notes);
}
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticatedClientSessionEntity implements Serializable {
private String authMethod;
private String redirectUri;
private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Set<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
}
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
}
@@ -17,61 +17,49 @@
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ClientSessionModel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionEntity extends SessionEntity {
public class AuthenticationSessionEntity extends SessionEntity {
private String client;
private String userSession;
private String authMethod;
private String clientUuid;
private String authUserId;
private String redirectUri;
private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();;
private String protocol;
private Map<String, String> clientNotes;
private Map<String, String> authNotes;
private Set<String> requiredActions = new HashSet<>();
private Map<String, String> userSessionNotes;
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
private Set<String> requiredActions = new HashSet<>();
public String getClient() {
return client;
public String getClientUuid() {
return clientUuid;
}
public void setClient(String client) {
this.client = client;
public void setClientUuid(String clientUuid) {
this.clientUuid = clientUuid;
}
public String getUserSession() {
return userSession;
public String getAuthUserId() {
return authUserId;
}
public void setUserSession(String userSession) {
this.userSession = userSession;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
public String getRedirectUri() {
@@ -114,28 +102,36 @@ public class ClientSessionEntity extends SessionEntity {
this.protocolMappers = protocolMappers;
}
public Map<String, String> getNotes() {
return notes;
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatus() {
return executionStatus;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
public void setExecutionStatus(Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus) {
this.executionStatus = executionStatus;
}
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus;
public String getProtocol() {
return protocol;
}
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getAuthUserId() {
return authUserId;
public Map<String, String> getClientNotes() {
return clientNotes;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
public void setClientNotes(Map<String, String> clientNotes) {
this.clientNotes = clientNotes;
}
public Set<String> getRequiredActions() {
return requiredActions;
}
public void setRequiredActions(Set<String> requiredActions) {
this.requiredActions = requiredActions;
}
public Map<String, String> getUserSessionNotes() {
@@ -146,7 +142,11 @@ public class ClientSessionEntity extends SessionEntity {
this.userSessionNotes = userSessionNotes;
}
public Set<String> getRequiredActions() {
return requiredActions;
public Map<String, String> getAuthNotes() {
return authNotes;
}
public void setAuthNotes(Map<String, String> authNotes) {
this.authNotes = authNotes;
}
}
@@ -46,12 +46,12 @@ public class UserSessionEntity extends SessionEntity {
private int lastSessionRefresh;
private Set<String> clientSessions = new CopyOnWriteArraySet<>();
private UserSessionModel.State state;
private Map<String, String> notes = new ConcurrentHashMap<>();
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions;
public String getUser() {
return user;
}
@@ -108,10 +108,6 @@ public class UserSessionEntity extends SessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
public Set<String> getClientSessions() {
return clientSessions;
}
public Map<String, String> getNotes() {
return notes;
}
@@ -120,6 +116,14 @@ public class UserSessionEntity extends SessionEntity {
this.notes = notes;
}
public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) {
this.authenticatedClientSessions = authenticatedClientSessions;
}
public UserSessionModel.State getState() {
return state;
}
@@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.initializer;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
@@ -64,12 +63,7 @@ public class OfflineUserSessionLoader implements SessionLoader {
for (UserSessionModel persistentSession : sessions) {
// Save to memory/infinispan
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
offlineClientSession.setUserSession(offlineUserSession);
}
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true, true);
}
return true;
@@ -1,129 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
public ClientSessionMapper(String realm) {
this.realm = realm;
}
private enum EmitValue {
KEY, ENTITY, USER_SESSION_AND_TIMESTAMP
}
private String realm;
private EmitValue emit = EmitValue.ENTITY;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireNullUserSession = false;
public static ClientSessionMapper create(String realm) {
return new ClientSessionMapper(realm);
}
public ClientSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
public ClientSessionMapper emitUserSessionAndTimestamp() {
emit = EmitValue.USER_SESSION_AND_TIMESTAMP;
return this;
}
public ClientSessionMapper client(String client) {
this.client = client;
return this;
}
public ClientSessionMapper userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionMapper expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionMapper requireNullUserSession(boolean requireNullUserSession) {
this.requireNullUserSession = requireNullUserSession;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return;
}
switch (emit) {
case KEY:
collector.emit(key, key);
break;
case ENTITY:
collector.emit(key, entity);
break;
case USER_SESSION_AND_TIMESTAMP:
if (entity.getUserSession() != null) {
collector.emit(entity.getUserSession(), entity.getTimestamp());
}
break;
}
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Collection;
/**
* Return all clientSessions attached to any from input list of userSessions
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
private String realm;
private Collection<String> userSessions;
private EmitValue emit = EmitValue.ENTITY;
private enum EmitValue {
KEY, ENTITY
}
public ClientSessionsOfUserSessionMapper(String realm, Collection<String> userSessions) {
this.realm = realm;
this.userSessions = userSessions;
}
public ClientSessionsOfUserSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector<String, Object> collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (userSessions.contains(entity.getUserSession())) {
switch (emit) {
case KEY:
collector.emit(entity.getId(), entity.getId());
break;
case ENTITY:
collector.emit(entity.getId(), entity);
break;
}
}
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticationSessionPredicate implements Predicate<Map.Entry<String, AuthenticationSessionEntity>>, Serializable {
private String realm;
private String client;
private String user;
private Integer expired;
//private String brokerSessionId;
//private String brokerUserId;
private AuthenticationSessionPredicate(String realm) {
this.realm = realm;
}
public static AuthenticationSessionPredicate create(String realm) {
return new AuthenticationSessionPredicate(realm);
}
public AuthenticationSessionPredicate user(String user) {
this.user = user;
return this;
}
public AuthenticationSessionPredicate client(String client) {
this.client = client;
return this;
}
public AuthenticationSessionPredicate expired(Integer expired) {
this.expired = expired;
return this;
}
// public UserSessionPredicate brokerSessionId(String id) {
// this.brokerSessionId = id;
// return this;
// }
// public UserSessionPredicate brokerUserId(String id) {
// this.brokerUserId = id;
// return this;
// }
@Override
public boolean test(Map.Entry<String, AuthenticationSessionEntity> entry) {
AuthenticationSessionEntity entity = entry.getValue();
if (!realm.equals(entity.getRealm())) {
return false;
}
if (user != null && !entity.getAuthUserId().equals(user)) {
return false;
}
if (client != null && !entity.getClientUuid().equals(client)) {
return false;
}
// if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
// return false;
// }
//
// if (brokerUserId != null && !brokerUserId.equals(entity.getBrokerUserId())) {
// return false;
// }
if (expired != null && entity.getTimestamp() > expired) {
return false;
}
return true;
}
}
@@ -1,114 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionPredicate implements Predicate<Map.Entry<String, SessionEntity>>, Serializable {
private String realm;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireUserSession = false;
private Boolean requireNullUserSession = false;
private ClientSessionPredicate(String realm) {
this.realm = realm;
}
public static ClientSessionPredicate create(String realm) {
return new ClientSessionPredicate(realm);
}
public ClientSessionPredicate client(String client) {
this.client = client;
return this;
}
public ClientSessionPredicate userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionPredicate expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionPredicate requireUserSession() {
requireUserSession = true;
return this;
}
public ClientSessionPredicate requireNullUserSession() {
requireNullUserSession = true;
return this;
}
@Override
public boolean test(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (!realm.equals(e.getRealm())) {
return false;
}
if (!(e instanceof ClientSessionEntity)) {
return false;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return false;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return false;
}
if (requireUserSession && entity.getUserSession() == null) {
return false;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return false;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return false;
}
return true;
}
}
@@ -18,6 +18,8 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Comparator;
@@ -38,4 +40,17 @@ public class Comparators {
}
}
public static Comparator<UserSessionEntity> userSessionLastSessionRefresh() {
return new UserSessionLastSessionRefreshComparator();
}
private static class UserSessionLastSessionRefreshComparator implements Comparator<UserSessionEntity>, Serializable {
@Override
public int compare(UserSessionEntity u1, UserSessionEntity u2) {
return u1.getLastSessionRefresh() - u2.getLastSessionRefresh();
}
}
}
@@ -18,10 +18,10 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
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 java.io.Serializable;
import java.util.Map;
@@ -33,10 +33,6 @@ import java.util.function.Function;
*/
public class Mappers {
public static Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp> clientSessionToUserSessionTimestamp() {
return new ClientSessionToUserSessionTimestampMapper();
}
public static Function<Map.Entry<String, Optional<UserSessionTimestamp>>, UserSessionTimestamp> userSessionTimestamp() {
return new UserSessionTimestampMapper();
}
@@ -49,23 +45,14 @@ public class Mappers {
return new SessionEntityMapper();
}
public static Function<Map.Entry<String, SessionEntity>, UserSessionEntity> userSessionEntity() {
return new UserSessionEntityMapper();
}
public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
return new LoginFailureIdMapper();
}
public static Function<Map.Entry<String, SessionEntity>, String> clientSessionToUserSessionId() {
return new ClientSessionToUserSessionIdMapper();
}
private static class ClientSessionToUserSessionTimestampMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp>, Serializable {
@Override
public UserSessionTimestamp apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return new UserSessionTimestamp(entity.getUserSession(), entity.getTimestamp());
}
}
private static class UserSessionTimestampMapper implements Function<Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>>, org.keycloak.models.sessions.infinispan.UserSessionTimestamp>, Serializable {
@Override
public org.keycloak.models.sessions.infinispan.UserSessionTimestamp apply(Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>> e) {
@@ -87,6 +74,13 @@ public class Mappers {
}
}
private static class UserSessionEntityMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionEntity>, Serializable {
@Override
public UserSessionEntity apply(Map.Entry<String, SessionEntity> entry) {
return (UserSessionEntity) entry.getValue();
}
}
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
@Override
public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
@@ -94,12 +88,4 @@ public class Mappers {
}
}
private static class ClientSessionToUserSessionIdMapper implements Function<Map.Entry<String, SessionEntity>, String>, Serializable {
@Override
public String apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return entity.getUserSession();
}
}
}
@@ -33,6 +33,8 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
private String user;
private String client;
private Integer expired;
private Integer expiredRefresh;
@@ -53,6 +55,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return this;
}
public UserSessionPredicate client(String clientUUID) {
this.client = clientUUID;
return this;
}
public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
this.expired = expired;
this.expiredRefresh = expiredRefresh;
@@ -87,6 +94,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return false;
}
if (client != null && (entity.getAuthenticatedClientSessions() == null || !entity.getAuthenticatedClientSessions().containsKey(client))) {
return false;
}
if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
return false;
}
@@ -0,0 +1 @@
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory
@@ -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.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory
@@ -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.models.sessions.infinispan.InfinispanStickySessionEncoderProviderFactory
@@ -512,6 +512,26 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush();
}
@Override
public int getActionTokenGeneratedByAdminLifespan() {
return getAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN, 12 * 60 * 60);
}
@Override
public void setActionTokenGeneratedByAdminLifespan(int actionTokenGeneratedByAdminLifespan) {
setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN, actionTokenGeneratedByAdminLifespan);
}
@Override
public int getActionTokenGeneratedByUserLifespan() {
return getAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, getAccessCodeLifespanUserAction());
}
@Override
public void setActionTokenGeneratedByUserLifespan(int actionTokenGeneratedByUserLifespan) {
setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, actionTokenGeneratedByUserLifespan);
}
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {
@@ -26,4 +26,8 @@ public interface RealmAttributes {
String DISPLAY_NAME_HTML = "displayNameHtml";
String ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN = "actionTokenGeneratedByAdminLifespan";
String ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan";
}
@@ -17,14 +17,14 @@
package org.keycloak.models.jpa.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.PersistentClientSessionAdapter;
import org.keycloak.models.session.PersistentAuthenticatedClientSessionAdapter;
import org.keycloak.models.session.PersistentClientSessionModel;
import org.keycloak.models.session.PersistentUserSessionAdapter;
import org.keycloak.models.session.PersistentUserSessionModel;
@@ -34,8 +34,9 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -68,12 +69,11 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void createClientSession(ClientSessionModel clientSession, boolean offline) {
PersistentClientSessionAdapter adapter = new PersistentClientSessionAdapter(clientSession);
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
PersistentClientSessionModel model = adapter.getUpdatedModel();
PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
entity.setClientSessionId(clientSession.getId());
entity.setClientId(clientSession.getClient().getId());
entity.setTimestamp(clientSession.getTimestamp());
String offlineStr = offlineToString(offline);
@@ -121,9 +121,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void removeClientSession(String clientSessionId, boolean offline) {
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
String offlineStr = offlineToString(offline);
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offlineStr));
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
@@ -227,14 +227,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
int j = 0;
for (UserSessionModel ss : result) {
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
boolean next = true;
while (next && j < clientSessions.size()) {
PersistentClientSessionEntity clientSession = clientSessions.get(j);
if (clientSession.getUserSessionId().equals(userSession.getId())) {
PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.add(clientSessAdapter);
PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
j++;
} else {
next = false;
@@ -243,6 +243,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
}
return result;
}
@@ -252,21 +253,20 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
List<ClientSessionModel> clientSessions = new LinkedList<>();
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
}
private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
ClientModel client = realm.getClientById(entity.getClientId());
PersistentClientSessionModel model = new PersistentClientSessionModel();
model.setClientSessionId(entity.getClientSessionId());
model.setClientId(entity.getClientId());
model.setUserSessionId(userSession.getId());
model.setUserId(userSession.getUser().getId());
model.setTimestamp(entity.getTimestamp());
model.setData(entity.getData());
return new PersistentClientSessionAdapter(model, realm, client, userSession);
return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
}
@Override
@@ -20,7 +20,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.session.UserSessionPersisterProviderFactory;
@@ -45,12 +45,10 @@ import java.io.Serializable;
public class PersistentClientSessionEntity {
@Id
@Column(name="CLIENT_SESSION_ID", length = 36)
protected String clientSessionId;
@Column(name = "USER_SESSION_ID", length = 36)
protected String userSessionId;
@Id
@Column(name="CLIENT_ID", length = 36)
protected String clientId;
@@ -64,14 +62,6 @@ public class PersistentClientSessionEntity {
@Column(name="DATA")
protected String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;
}
@@ -114,20 +104,27 @@ public class PersistentClientSessionEntity {
public static class Key implements Serializable {
protected String clientSessionId;
protected String userSessionId;
protected String clientId;
protected String offline;
public Key() {
}
public Key(String clientSessionId, String offline) {
this.clientSessionId = clientSessionId;
public Key(String userSessionId, String clientId, String offline) {
this.userSessionId = userSessionId;
this.clientId = clientId;
this.offline = offline;
}
public String getClientSessionId() {
return clientSessionId;
public String getUserSessionId() {
return userSessionId;
}
public String getClientId() {
return clientId;
}
public String getOffline() {
@@ -141,7 +138,8 @@ public class PersistentClientSessionEntity {
Key key = (Key) o;
if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
if (this.clientId != null ? !this.clientId.equals(key.clientId) : key.clientId != null) return false;
if (this.offline != null ? !this.offline.equals(key.offline) : key.offline != null) return false;
return true;
@@ -149,7 +147,8 @@ public class PersistentClientSessionEntity {
@Override
public int hashCode() {
int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
result = 37 * result + (this.clientId != null ? this.clientId.hashCode() : 0);
result = 31 * result + (this.offline != null ? this.offline.hashCode() : 0);
return result;
}
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="mposolda@redhat.com" id="3.2.0">
<dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
<dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
</changeSet>
</databaseChangeLog>
@@ -47,4 +47,5 @@
<include file="META-INF/jpa-changelog-2.5.0.xml"/>
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
</databaseChangeLog>