KEYCLOAK-5688 Add externalizers for session entities
and remove unused events
This commit is contained in:
parent
d01be82b35
commit
6d18ba4b32
28 changed files with 291 additions and 256 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<>());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue