Merge pull request #4568 from hmlnarik/KEYCLOAK-5688-Externalizer-for-entities-shared-across-DC

KEYCLOAK-5688 Externalizer for entities shared across dc
This commit is contained in:
Hynek Mlnařík 2017-10-18 22:19:27 +02:00 committed by GitHub
commit 4c2ddfdf54
33 changed files with 322 additions and 259 deletions

View file

@ -31,6 +31,10 @@
<artifactId>keycloak-distribution-parent</artifactId>
<packaging>pom</packaging>
<properties>
<keycloak.provisioning.xml>server-provisioning.xml</keycloak.provisioning.xml>
</properties>
<modules>
<module>adapters</module>
<module>saml-adapters</module>

View file

@ -68,7 +68,7 @@
</goals>
<phase>compile</phase>
<configuration>
<config-file>../server-provisioning.xml</config-file>
<config-file>../${keycloak.provisioning.xml}</config-file>
</configuration>
</execution>
</executions>

View file

@ -82,7 +82,7 @@
</goals>
<phase>compile</phase>
<configuration>
<config-file>../server-provisioning.xml</config-file>
<config-file>../${keycloak.provisioning.xml}</config-file>
<overlay>true</overlay>
</configuration>
</execution>

View file

@ -0,0 +1,24 @@
<!--
~ 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.
-->
<server-provisioning xmlns="urn:wildfly:server-provisioning:1.2" extract-schemas="true">
<copy-artifacts>
<copy-artifact artifact="org.keycloak:keycloak-client-cli-dist:zip" to-location="" from-location="keycloak-client-tools"/>
</copy-artifacts>
<feature-packs>
<feature-pack groupId="org.keycloak" artifactId="keycloak-server-feature-pack" version="${project.version}"/>
</feature-packs>
</server-provisioning>

View file

@ -19,6 +19,6 @@
<copy-artifact artifact="org.keycloak:keycloak-client-cli-dist:zip" to-location="" from-location="keycloak-client-tools"/>
</copy-artifacts>
<feature-packs>
<feature-pack groupId="org.keycloak" artifactId="keycloak-server-feature-pack" version="${project.version}"/>
<feature-pack groupId="org.keycloak" artifactId="keycloak-server-feature-pack" version="${project.version}"/>
</feature-packs>
</server-provisioning>

View file

@ -1,50 +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.cache.infinispan.events;
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;
}
}

View file

@ -17,19 +17,33 @@
package org.keycloak.models.cache.infinispan.events;
import org.keycloak.cluster.ClusterEvent;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith;
/**
*
* @author hmlnarik
*/
@SerializeWith(AuthenticationSessionAuthNoteUpdateEvent.ExternalizerImpl.class)
public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
private String authSessionId;
private Map<String, String> authNotesFragment;
/**
* Creates an instance of the event.
* @param authSessionId
* @param authNotesFragment
* @return Event. Note that {@code authNotesFragment} property is not thread safe which is fine for now.
*/
public static AuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, Map<String, String> authNotesFragment) {
AuthenticationSessionAuthNoteUpdateEvent event = new AuthenticationSessionAuthNoteUpdateEvent();
event.authSessionId = authSessionId;
@ -50,4 +64,34 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, authNotesFragment=%s ]", authSessionId, authNotesFragment);
}
public static class ExternalizerImpl implements Externalizer<AuthenticationSessionAuthNoteUpdateEvent> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, AuthenticationSessionAuthNoteUpdateEvent value) throws IOException {
output.write(VERSION_1);
MarshallUtil.marshallString(value.authSessionId, output);
MarshallUtil.marshallMap(value.authNotesFragment, output);
}
@Override
public AuthenticationSessionAuthNoteUpdateEvent readObject(ObjectInput input) throws IOException, ClassNotFoundException {
switch (input.readByte()) {
case VERSION_1:
return readObjectVersion1(input);
default:
throw new IOException("Unknown version");
}
}
public AuthenticationSessionAuthNoteUpdateEvent readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
return create(
MarshallUtil.unmarshallString(input),
MarshallUtil.unmarshallMap(input, (size) -> new HashMap<>(size))
);
}
}
}

View file

@ -1,42 +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.cache.infinispan.events;
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;
}
}

View file

@ -323,7 +323,7 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
String id = entity.getId();
entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setRealmId(realm.getId());
entity.setClientUuid(client.getId());
entity.setTimestamp(Time.currentTime());
update();

View file

@ -17,11 +17,9 @@
package org.keycloak.models.sessions.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
@ -99,14 +97,4 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
return value;
}
@Override
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);
}
}

View file

@ -18,21 +18,12 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.infinispan.remoting.transport.Address;
import org.jboss.logging.Logger;
/**
*
@ -40,17 +31,10 @@ import org.jboss.logging.Logger;
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
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
@ -66,32 +50,6 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
final Address cacheAddress = cache.getCacheManager().getAddress();
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
AdvancedCache<ActionTokenReducedKey, ActionTokenValueEntity> localCache = cache
.getAdvancedCache()
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD);
List<ActionTokenReducedKey> toRemove = localCache
.keySet()
.stream()
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
.collect(Collectors.toList());
toRemove.forEach(localCache::remove);
});
LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
return cache;
}

View file

@ -71,7 +71,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
public AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client) {
AuthenticationSessionEntity entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setRealmId(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClientUuid(client.getId());

View file

@ -172,7 +172,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
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.setRealmId(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
entity.setIpAddress(ipAddress);
@ -525,7 +525,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) {
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
LoginFailureEntity entity = new LoginFailureEntity();
entity.setRealm(realm.getId());
entity.setRealmId(realm.getId());
entity.setUserId(userId);
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = new SessionUpdateTask<LoginFailureEntity>() {
@ -768,7 +768,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(userSession.getId());
entity.setRealm(userSession.getRealm().getId());
entity.setRealmId(userSession.getRealm().getId());
entity.setAuthMethod(userSession.getAuthMethod());
entity.setBrokerSessionId(userSession.getBrokerSessionId());

View file

@ -63,7 +63,7 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
return;
}
RealmModel realm = kcSession.realms().getRealm(wrappedEntity.getEntity().getRealm());
RealmModel realm = kcSession.realms().getRealm(wrappedEntity.getEntity().getRealmId());
myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
updates.put(key, myUpdates);
@ -81,7 +81,7 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
throw new IllegalArgumentException("Null entity not allowed");
}
RealmModel realm = kcSession.realms().getRealm(entity.getRealm());
RealmModel realm = kcSession.realms().getRealm(entity.getRealmId());
SessionEntityWrapper<V> wrappedEntity = new SessionEntityWrapper<>(entity);
SessionUpdatesList<V> myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
updates.put(key, myUpdates);
@ -121,7 +121,7 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
return null;
}
RealmModel realm = kcSession.realms().getRealm(wrappedEntity.getEntity().getRealm());
RealmModel realm = kcSession.realms().getRealm(wrappedEntity.getEntity().getRealmId());
myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
updates.put(key, myUpdates);

View file

@ -40,7 +40,7 @@ public class AuthenticationSessionEntity extends SessionEntity {
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();;
private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
private String protocol;
private Map<String, String> clientNotes;
@ -179,6 +179,6 @@ public class AuthenticationSessionEntity extends SessionEntity {
@Override
public String toString() {
return String.format("AuthenticationSessionEntity [id=%s, realm=%s, clientUuid=%s ]", getId(), getRealm(), getClientUuid());
return String.format("AuthenticationSessionEntity [id=%s, realm=%s, clientUuid=%s ]", getId(), getRealmId(), getClientUuid());
}
}

View file

@ -17,9 +17,17 @@
package org.keycloak.models.sessions.infinispan.entities;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@SerializeWith(LoginFailureEntity.ExternalizerImpl.class)
public class LoginFailureEntity extends SessionEntity {
private String userId;
@ -28,6 +36,18 @@ public class LoginFailureEntity extends SessionEntity {
private long lastFailure;
private String lastIPFailure;
public LoginFailureEntity() {
}
private LoginFailureEntity(String realmId, String userId, int failedLoginNotBefore, int numFailures, long lastFailure, String lastIPFailure) {
super(realmId);
this.userId = userId;
this.failedLoginNotBefore = failedLoginNotBefore;
this.numFailures = numFailures;
this.lastFailure = lastFailure;
this.lastIPFailure = lastIPFailure;
}
public String getUserId() {
return userId;
}
@ -83,7 +103,7 @@ public class LoginFailureEntity extends SessionEntity {
LoginFailureEntity that = (LoginFailureEntity) o;
if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
if (getRealm() != null ? !getRealm().equals(that.getRealm()) : that.getRealm() != null) return false;
if (getRealmId() != null ? !getRealmId().equals(that.getRealmId()) : that.getRealmId() != null) return false;
return true;
@ -91,13 +111,51 @@ public class LoginFailureEntity extends SessionEntity {
@Override
public int hashCode() {
int hashCode = getRealm() != null ? getRealm().hashCode() : 0;
int hashCode = getRealmId() != null ? getRealmId().hashCode() : 0;
hashCode = hashCode * 13 + (userId != null ? userId.hashCode() : 0);
return hashCode;
}
@Override
public String toString() {
return String.format("LoginFailureEntity [ userId=%s, realm=%s, numFailures=%d ]", userId, getRealm(), numFailures);
return String.format("LoginFailureEntity [ userId=%s, realm=%s, numFailures=%d ]", userId, getRealmId(), numFailures);
}
public static class ExternalizerImpl implements Externalizer<LoginFailureEntity> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, LoginFailureEntity value) throws IOException {
output.writeByte(VERSION_1);
MarshallUtil.marshallString(value.getRealmId(), output);
MarshallUtil.marshallString(value.userId, output);
output.writeInt(value.failedLoginNotBefore);
output.writeInt(value.numFailures);
output.writeLong(value.lastFailure);
MarshallUtil.marshallString(value.lastIPFailure, output);
}
@Override
public LoginFailureEntity readObject(ObjectInput input) throws IOException {
switch (input.readByte()) {
case VERSION_1:
return readObjectVersion1(input);
default:
throw new IOException("Unknown version");
}
}
public LoginFailureEntity readObjectVersion1(ObjectInput input) throws IOException {
return new LoginFailureEntity(
MarshallUtil.unmarshallString(input),
MarshallUtil.unmarshallString(input),
input.readInt(),
input.readInt(),
input.readLong(),
MarshallUtil.unmarshallString(input)
);
}
}
}

View file

@ -17,18 +17,24 @@
package org.keycloak.models.sessions.infinispan.entities;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginFailureKey implements Serializable {
@SerializeWith(LoginFailureKey.ExternalizerImpl.class)
public class LoginFailureKey {
private final String realm;
private final String realmId;
private final String userId;
public LoginFailureKey(String realm, String userId) {
this.realm = realm;
public LoginFailureKey(String realmId, String userId) {
this.realmId = realmId;
this.userId = userId;
}
@ -39,7 +45,7 @@ public class LoginFailureKey implements Serializable {
LoginFailureKey key = (LoginFailureKey) o;
if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
if (realmId != null ? !realmId.equals(key.realmId) : key.realmId != null) return false;
if (userId != null ? !userId.equals(key.userId) : key.userId != null) return false;
return true;
@ -47,7 +53,7 @@ public class LoginFailureKey implements Serializable {
@Override
public int hashCode() {
int result = realm != null ? realm.hashCode() : 0;
int result = realmId != null ? realmId.hashCode() : 0;
result = 31 * result + (userId != null ? userId.hashCode() : 0);
return result;
}
@ -55,6 +61,33 @@ public class LoginFailureKey implements Serializable {
@Override
public String toString() {
return String.format("LoginFailureKey [ realm=%s. userId=%s ]", realm, userId);
return String.format("LoginFailureKey [ realmId=%s. userId=%s ]", realmId, userId);
}
public static class ExternalizerImpl implements Externalizer<LoginFailureKey> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, LoginFailureKey value) throws IOException {
output.writeByte(VERSION_1);
MarshallUtil.marshallString(value.realmId, output);
MarshallUtil.marshallString(value.userId, output);
}
@Override
public LoginFailureKey readObject(ObjectInput input) throws IOException, ClassNotFoundException {
switch (input.readByte()) {
case VERSION_1:
return readObjectVersion1(input);
default:
throw new IOException("Unknown version");
}
}
public LoginFailureKey readObjectVersion1(ObjectInput input) throws IOException {
return new LoginFailureKey(MarshallUtil.unmarshallString(input), MarshallUtil.unmarshallString(input));
}
}
}

View file

@ -26,17 +26,26 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
*/
public abstract class SessionEntity implements Serializable {
private String realm;
private String realmId;
public String getRealm() {
return realm;
/**
* Returns realmId ID.
* @return
*/
public String getRealmId() {
return realmId;
}
public void setRealm(String realm) {
this.realm = realm;
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public SessionEntity() {
}
protected SessionEntity(String realmId) {
this.realmId = realmId;
}
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
if (localEntityWrapper == null) {

View file

@ -22,13 +22,17 @@ import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith;
import org.jboss.logging.Logger;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionModel.State;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
@ -191,7 +195,7 @@ public class UserSessionEntity extends SessionEntity {
@Override
public String toString() {
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealm(), getLastSessionRefresh(),
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealmId(), getLastSessionRefresh(),
new TreeSet(this.authenticatedClientSessions.keySet()));
}
@ -225,6 +229,18 @@ public class UserSessionEntity extends SessionEntity {
private static final int VERSION_1 = 1;
private static final EnumMap<UserSessionModel.State, Integer> STATE_TO_ID = new EnumMap<>(UserSessionModel.State.class);
private static final Map<Integer, UserSessionModel.State> ID_TO_STATE = new HashMap<>();
static {
STATE_TO_ID.put(State.LOGGED_IN, 1);
STATE_TO_ID.put(State.LOGGED_OUT, 2);
STATE_TO_ID.put(State.LOGGING_OUT, 3);
for (Entry<State, Integer> entry : STATE_TO_ID.entrySet()) {
ID_TO_STATE.put(entry.getValue(), entry.getKey());
}
}
@Override
public void writeObject(ObjectOutput output, UserSessionEntity session) throws IOException {
output.writeByte(VERSION_1);
@ -235,15 +251,14 @@ public class UserSessionEntity extends SessionEntity {
MarshallUtil.marshallString(session.getId(), output);
MarshallUtil.marshallString(session.getIpAddress(), output);
MarshallUtil.marshallString(session.getLoginUsername(), output);
MarshallUtil.marshallString(session.getRealm(), output);
MarshallUtil.marshallString(session.getRealmId(), output);
MarshallUtil.marshallString(session.getUser(), output);
MarshallUtil.marshallInt(output, session.getLastSessionRefresh());
MarshallUtil.marshallInt(output, session.getStarted());
output.writeBoolean(session.isRememberMe());
int state = session.getState() == null ? 0 :
((session.getState() == UserSessionModel.State.LOGGED_IN) ? 1 : (session.getState() == UserSessionModel.State.LOGGED_OUT ? 2 : 3));
int state = session.getState() == null ? 0 : STATE_TO_ID.get(session.getState());
output.writeInt(state);
Map<String, String> notes = session.getNotes();
@ -273,24 +288,14 @@ public class UserSessionEntity extends SessionEntity {
sessionEntity.setId(MarshallUtil.unmarshallString(input));
sessionEntity.setIpAddress(MarshallUtil.unmarshallString(input));
sessionEntity.setLoginUsername(MarshallUtil.unmarshallString(input));
sessionEntity.setRealm(MarshallUtil.unmarshallString(input));
sessionEntity.setRealmId(MarshallUtil.unmarshallString(input));
sessionEntity.setUser(MarshallUtil.unmarshallString(input));
sessionEntity.setLastSessionRefresh(MarshallUtil.unmarshallInt(input));
sessionEntity.setStarted(MarshallUtil.unmarshallInt(input));
sessionEntity.setRememberMe(input.readBoolean());
int state = input.readInt();
switch(state) {
case 1: sessionEntity.setState(UserSessionModel.State.LOGGED_IN);
break;
case 2: sessionEntity.setState(UserSessionModel.State.LOGGED_OUT);
break;
case 3: sessionEntity.setState(UserSessionModel.State.LOGGING_OUT);
break;
default:
sessionEntity.setState(null);
}
sessionEntity.setState(ID_TO_STATE.get(input.readInt()));
Map<String, String> notes = KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT,
new KeycloakMarshallUtil.ConcurrentHashMapBuilder<>());

View file

@ -96,8 +96,7 @@ public abstract class BaseCacheInitializer extends CacheInitializer {
});
state = new InitializerState();
state.init(count[0], sessionsPerSegment);
state = new InitializerState(count[0], sessionsPerSegment);
saveStateToCache(state);
}
return state;

View file

@ -124,9 +124,7 @@ public class InfinispanCacheInitializer extends BaseCacheInitializer {
saveStateToCache(state);
if (log.isDebugEnabled()) {
log.debug("New initializer state pushed. The state is: " + state.printState());
}
log.debugf("New initializer state pushed. The state is: %s", state);
}
// Loader callback after the task is finished

View file

@ -20,50 +20,70 @@ package org.keycloak.models.sessions.infinispan.initializer;
import org.jboss.logging.Logger;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.util.ArrayList;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith;
/**
* Note that this state is <b>NOT</b> thread safe. Currently it is only used from single thread so it's fine
* but further optimizations might need to revisit this (see {@link InfinispanCacheInitializer}).
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@SerializeWith(InitializerState.ExternalizerImpl.class)
public class InitializerState extends SessionEntity {
private static final Logger log = Logger.getLogger(InitializerState.class);
private int sessionsCount;
private List<Boolean> segments = new ArrayList<>();
private final int sessionsCount;
private final int segmentsCount;
private final BitSet segments;
private int lowestUnfinishedSegment = 0;
public void init(int sessionsCount, int sessionsPerSegment) {
public InitializerState(int sessionsCount, int sessionsPerSegment) {
this.sessionsCount = sessionsCount;
int segmentsCount = sessionsCount / sessionsPerSegment;
if (sessionsPerSegment * segmentsCount < sessionsCount) {
segmentsCount = segmentsCount + 1;
int segmentsCountLocal = sessionsCount / sessionsPerSegment;
if (sessionsPerSegment * segmentsCountLocal < sessionsCount) {
segmentsCountLocal = segmentsCountLocal + 1;
}
this.segmentsCount = segmentsCountLocal;
this.segments = new BitSet(segmentsCountLocal);
log.debugf("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCount);
for (int i=0 ; i<segmentsCount ; i++) {
segments.add(false);
}
log.debugf("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCountLocal);
updateLowestUnfinishedSegment();
}
// Return true just if computation is entirely finished (all segments are true)
public boolean isFinished() {
return lowestUnfinishedSegment == -1;
private InitializerState(String realmId, int sessionsCount, int segmentsCount, BitSet segments) {
super(realmId);
this.sessionsCount = sessionsCount;
this.segmentsCount = segmentsCount;
this.segments = segments;
log.debugf("sessionsCount: %d, segmentsCount: %d", sessionsCount, segmentsCount);
updateLowestUnfinishedSegment();
}
// Return next un-finished segments. It can return "segmentCount" segments or less
public List<Integer> getUnfinishedSegments(int segmentCount) {
List<Integer> result = new ArrayList<>();
/** Return true just if computation is entirely finished (all segments are true) */
public boolean isFinished() {
return segments.cardinality() == segmentsCount;
}
/** Return next un-finished segments. It returns at most {@code maxSegmentCount} segments. */
public List<Integer> getUnfinishedSegments(int maxSegmentCount) {
List<Integer> result = new LinkedList<>();
int next = lowestUnfinishedSegment;
boolean remaining = lowestUnfinishedSegment != -1;
while (remaining && result.size() < segmentCount) {
while (remaining && result.size() < maxSegmentCount) {
next = getNextUnfinishedSegmentFromIndex(next);
if (next == -1) {
remaining = false;
@ -77,7 +97,7 @@ public class InitializerState extends SessionEntity {
}
public void markSegmentFinished(int index) {
segments.set(index, true);
segments.set(index);
updateLowestUnfinishedSegment();
}
@ -86,35 +106,55 @@ public class InitializerState extends SessionEntity {
}
private int getNextUnfinishedSegmentFromIndex(int index) {
int segmentsSize = segments.size();
for (int i=index ; i<segmentsSize ; i++) {
Boolean entry = segments.get(i);
if (!entry) {
return i;
}
}
return -1;
final int nextFreeSegment = this.segments.nextClearBit(index);
return (nextFreeSegment < this.segmentsCount)
? nextFreeSegment
: -1;
}
public String printState() {
int finished = 0;
int nonFinished = 0;
@Override
public String toString() {
int finished = segments.cardinality();
int nonFinished = this.segmentsCount;
int size = segments.size();
for (int i=0 ; i<size ; i++) {
Boolean done = segments.get(i);
if (done) {
finished++;
} else {
nonFinished++;
return "sessionsCount: "
+ sessionsCount
+ (", finished segments count: " + finished)
+ (", non-finished segments count: " + nonFinished);
}
public static class ExternalizerImpl implements Externalizer<InitializerState> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, InitializerState value) throws IOException {
output.writeByte(VERSION_1);
MarshallUtil.marshallString(value.getRealmId(), output);
output.writeInt(value.sessionsCount);
output.writeInt(value.segmentsCount);
MarshallUtil.marshallByteArray(value.segments.toByteArray(), output);
}
@Override
public InitializerState readObject(ObjectInput input) throws IOException, ClassNotFoundException {
switch (input.readByte()) {
case VERSION_1:
return readObjectVersion1(input);
default:
throw new IOException("Unknown version");
}
}
StringBuilder strBuilder = new StringBuilder("sessionsCount: " + sessionsCount)
.append(", finished segments count: " + finished)
.append(", non-finished segments count: " + nonFinished);
public InitializerState readObjectVersion1(ObjectInput input) throws IOException {
return new InitializerState(
MarshallUtil.unmarshallString(input),
input.readInt(),
input.readInt(),
BitSet.valueOf(MarshallUtil.unmarshallByteArray(input))
);
}
return strBuilder.toString();
}
}

View file

@ -76,7 +76,7 @@ public class AuthenticationSessionPredicate implements Predicate<Map.Entry<Strin
public boolean test(Map.Entry<String, AuthenticationSessionEntity> entry) {
AuthenticationSessionEntity entity = entry.getValue();
if (!realm.equals(entity.getRealm())) {
if (!realm.equals(entity.getRealmId())) {
return false;
}

View file

@ -41,7 +41,7 @@ public class SessionPredicate<S extends SessionEntity> implements Predicate<Map.
@Override
public boolean test(Map.Entry<String, SessionEntityWrapper<S>> entry) {
return realm.equals(entry.getValue().getEntity().getRealm());
return realm.equals(entry.getValue().getEntity().getRealmId());
}
}

View file

@ -43,7 +43,7 @@ public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailu
@Override
public boolean test(Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> entry) {
LoginFailureEntity e = entry.getValue().getEntity();
return realm.equals(e.getRealm());
return realm.equals(e.getRealmId());
}
}

View file

@ -83,7 +83,7 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
UserSessionEntity entity = (UserSessionEntity) e;
if (!realm.equals(entity.getRealm())) {
if (!realm.equals(entity.getRealmId())) {
return false;
}

View file

@ -162,7 +162,7 @@ public class ConcurrencyJDGRemoveSessionTest {
// Create 100 initial sessions
UserSessionEntity session = new UserSessionEntity();
session.setId(sessionId);
session.setRealm("foo");
session.setRealmId("foo");
session.setBrokerSessionId("!23123123");
session.setBrokerUserId(null);
session.setUser("foo");

View file

@ -75,7 +75,7 @@ public class ConcurrencyJDGSessionsCacheTest {
// Create initial item
UserSessionEntity session = new UserSessionEntity();
session.setId("123");
session.setRealm("foo");
session.setRealmId("foo");
session.setBrokerSessionId("!23123123");
session.setBrokerUserId(null);
session.setUser("foo");

View file

@ -59,7 +59,7 @@ public class DistributedCacheConcurrentWritesTest {
// Create initial item
UserSessionEntity session = new UserSessionEntity();
session.setId("123");
session.setRealm("foo");
session.setRealmId("foo");
session.setBrokerSessionId("!23123123");
session.setBrokerUserId(null);
session.setUser("foo");
@ -169,7 +169,7 @@ public class DistributedCacheConcurrentWritesTest {
private static UserSessionEntity cloneSession(UserSessionEntity session) {
UserSessionEntity clone = new UserSessionEntity();
clone.setId(session.getId());
clone.setRealm(session.getRealm());
clone.setRealmId(session.getRealmId());
clone.setNotes(new ConcurrentHashMap<>(session.getNotes()));
return clone;
}

View file

@ -60,7 +60,7 @@ public class DistributedCacheWriteSkewTest {
// Create initial item
UserSessionEntity session = new UserSessionEntity();
session.setId("123");
session.setRealm("foo");
session.setRealmId("foo");
session.setBrokerSessionId("!23123123");
session.setBrokerUserId(null);
session.setUser("foo");

View file

@ -33,8 +33,7 @@ public class InitializerStateTest {
@Test
public void testComputationState() {
InitializerState state = new InitializerState();
state.init(28, 5);
InitializerState state = new InitializerState(28, 5);
Assert.assertFalse(state.isFinished());
List<Integer> segments = state.getUnfinishedSegments(3);

View file

@ -51,8 +51,4 @@ public interface ActionTokenStoreProvider extends Provider {
* @return {@code null} if no token is found for given key and nonce, value otherwise
*/
ActionTokenValueModel remove(ActionTokenKeyModel key);
void removeAll(String userId, String actionId);
}

View file

@ -73,7 +73,7 @@ public abstract class AbstractSessionCacheCommand extends AbstractCommand {
protected String toString(UserSessionEntity userSession) {
int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealmId()+ ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
", authenticatedClientSessions: " + clientSessionsSize;
}
@ -100,7 +100,7 @@ public abstract class AbstractSessionCacheCommand extends AbstractCommand {
String id = getArg(1);
userSession.setId(id);
userSession.setRealm(getArg(2));
userSession.setRealmId(getArg(2));
userSession.setLastSessionRefresh(Time.currentTime());
cache.put(id, new SessionEntityWrapper(userSession));
@ -287,7 +287,7 @@ public abstract class AbstractSessionCacheCommand extends AbstractCommand {
String id = KeycloakModelUtils.generateId();
userSession.setId(id);
userSession.setRealm(realmName);
userSession.setRealmId(realmName);
userSession.setLastSessionRefresh(Time.currentTime());
cache.put(id, new SessionEntityWrapper(userSession));