Merge pull request #4132 from mposolda/cross-dc4-squash
KEYCLOAK-4626 KEYCLOAK-4627 Authentication sessions & Action tokens
This commit is contained in:
commit
70d7e07526
324 files changed
+12794
-4243
No files matched your search
+55
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -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);
|
||||
|
||||
|
||||
+50
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -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();
|
||||
|
||||
+42
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+12
@@ -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;
|
||||
}
|
||||
|
||||
+53
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+197
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+121
-82
@@ -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;
|
||||
|
||||
+98
@@ -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);
|
||||
}
|
||||
}
|
||||
+100
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+113
@@ -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";
|
||||
}
|
||||
}
|
||||
+218
@@ -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);
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+58
@@ -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";
|
||||
}
|
||||
}
|
||||
+106
-393
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+50
-14
@@ -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;
|
||||
}
|
||||
}
|
||||
+104
@@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+47
-47
@@ -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;
|
||||
}
|
||||
}
|
||||
+10
-6
@@ -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;
|
||||
}
|
||||
|
||||
+1
-7
@@ -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;
|
||||
|
||||
-129
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-77
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -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;
|
||||
}
|
||||
}
|
||||
-114
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+12
-26
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory
|
||||
+18
@@ -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
|
||||
+18
@@ -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";
|
||||
|
||||
}
|
||||
+15
-15
@@ -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
|
||||
|
||||
-1
@@ -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;
|
||||
|
||||
|
||||
+17
-18
@@ -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>
|
||||
Reference in new issue
Block a user