From 6d18ba4b3229428d14395b0cdbcf28c5721263bd Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Wed, 18 Oct 2017 15:11:05 +0200 Subject: [PATCH] KEYCLOAK-5688 Add externalizers for session entities and remove unused events --- .../AddInvalidatedActionTokenEvent.java | 50 ------- ...henticationSessionAuthNoteUpdateEvent.java | 44 +++++++ .../RemoveActionTokensSpecificEvent.java | 42 ------ .../AuthenticationSessionAdapter.java | 2 +- .../InfinispanActionTokenStoreProvider.java | 12 -- ...nispanActionTokenStoreProviderFactory.java | 42 ------ ...finispanAuthenticationSessionProvider.java | 2 +- .../InfinispanUserSessionProvider.java | 6 +- .../InfinispanChangelogBasedTransaction.java | 6 +- .../entities/AuthenticationSessionEntity.java | 4 +- .../entities/LoginFailureEntity.java | 64 ++++++++- .../infinispan/entities/LoginFailureKey.java | 49 +++++-- .../infinispan/entities/SessionEntity.java | 21 ++- .../entities/UserSessionEntity.java | 37 +++--- .../initializer/BaseCacheInitializer.java | 3 +- .../InfinispanCacheInitializer.java | 4 +- .../initializer/InitializerState.java | 122 ++++++++++++------ .../AuthenticationSessionPredicate.java | 2 +- .../infinispan/stream/SessionPredicate.java | 2 +- .../stream/UserLoginFailurePredicate.java | 2 +- .../stream/UserSessionPredicate.java | 2 +- .../ConcurrencyJDGRemoveSessionTest.java | 2 +- .../ConcurrencyJDGSessionsCacheTest.java | 2 +- .../DistributedCacheConcurrentWritesTest.java | 4 +- .../DistributedCacheWriteSkewTest.java | 2 +- .../initializer/InitializerStateTest.java | 3 +- .../models/ActionTokenStoreProvider.java | 4 - .../util/cli/AbstractSessionCacheCommand.java | 6 +- 28 files changed, 288 insertions(+), 253 deletions(-) delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java deleted file mode 100644 index 2bced7e4517a..000000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java +++ /dev/null @@ -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; - } - -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java index d7bdcdfcceca..e56d4a40439d 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java @@ -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 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 authNotesFragment) { AuthenticationSessionAuthNoteUpdateEvent event = new AuthenticationSessionAuthNoteUpdateEvent(); event.authSessionId = authSessionId; @@ -50,4 +64,34 @@ public String toString() { return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, authNotesFragment=%s ]", authSessionId, authNotesFragment); } + public static class ExternalizerImpl implements Externalizer { + + 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)) + ); + } + + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java deleted file mode 100644 index d658f30c589d..000000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java +++ /dev/null @@ -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; - } - -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java index 05a762b54fca..5736188623bb 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java @@ -323,7 +323,7 @@ public void restartSession(RealmModel realm, ClientModel client) { 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(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java index 2a31453a94c2..8fd6f54215be 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java @@ -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 ActionTokenValueModel remove(ActionTokenKeyModel actionTokenKey) { 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); - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java index e4f3bd0c08fd..63882dc80c25 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java @@ -18,21 +18,12 @@ 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 @@ */ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory { - private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class); - private volatile Cache 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 void init(Scope config) { private static Cache initActionTokenCache(KeycloakSession session) { InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class); Cache 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 localCache = cache - .getAdvancedCache() - .withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD); - - List 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; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java index a2b8a873ffc5..5ae841632d23 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java @@ -71,7 +71,7 @@ public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, 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()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index f9e21e18defc..8b6df90c3d30 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -172,7 +172,7 @@ public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper createLoginFailureTask = new SessionUpdateTask() { @@ -768,7 +768,7 @@ public List getOfflineUserSessions(RealmModel realm, ClientMod 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()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java index 195099258d07..694c41e461be 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java @@ -63,7 +63,7 @@ public void addTask(K key, SessionUpdateTask task) { 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 void addTask(K key, SessionUpdateTask task, V entity) { throw new IllegalArgumentException("Null entity not allowed"); } - RealmModel realm = kcSession.realms().getRealm(entity.getRealm()); + RealmModel realm = kcSession.realms().getRealm(entity.getRealmId()); SessionEntityWrapper wrappedEntity = new SessionEntityWrapper<>(entity); SessionUpdatesList myUpdates = new SessionUpdatesList<>(realm, wrappedEntity); updates.put(key, myUpdates); @@ -121,7 +121,7 @@ public SessionEntityWrapper get(K key) { 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); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java index 39cbdae1a04d..3d7d9af3d5f0 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java @@ -40,7 +40,7 @@ public class AuthenticationSessionEntity extends SessionEntity { private Set roles; private Set protocolMappers; - private Map executionStatus = new HashMap<>();; + private Map executionStatus = new HashMap<>(); private String protocol; private Map clientNotes; @@ -179,6 +179,6 @@ public int hashCode() { @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()); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java index 5b328ecc915a..49f0fa641529 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java @@ -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 Stian Thorgersen */ +@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 boolean equals(Object o) { 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 boolean equals(Object o) { @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 { + + 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) + ); + } } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java index 318b1ba54792..484aec71fefb 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java @@ -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 Stian Thorgersen */ -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 boolean equals(Object o) { 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 boolean equals(Object o) { @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 int hashCode() { @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 { + + 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)); + } } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java index 37f8e081ce33..6d93e19d3921 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java @@ -26,17 +26,26 @@ */ public abstract class SessionEntity implements Serializable { - private String realm; - + private String realmId; + + /** + * Returns realmId ID. + * @return + */ + public String getRealmId() { + return realmId; + } - public String getRealm() { - return realm; + public void setRealmId(String realmId) { + this.realmId = realmId; } - public void setRealm(String realm) { - this.realm = realm; + public SessionEntity() { } + protected SessionEntity(String realmId) { + this.realmId = realmId; + } public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) { if (localEntityWrapper == null) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java index 8ac85a17e482..78df451b602f 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java @@ -22,13 +22,17 @@ 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 int hashCode() { @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 static class ExternalizerImpl implements Externalizer private static final int VERSION_1 = 1; + private static final EnumMap STATE_TO_ID = new EnumMap<>(UserSessionModel.State.class); + private static final Map 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 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 void writeObject(ObjectOutput output, UserSessionEntity session) throws I 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 notes = session.getNotes(); @@ -273,24 +288,14 @@ public UserSessionEntity readObjectVersion1(ObjectInput input) throws IOExceptio 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 notes = KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, new KeycloakMarshallUtil.ConcurrentHashMapBuilder<>()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java index cca28cc65609..40065d5b552b 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java @@ -96,8 +96,7 @@ public void run(KeycloakSession session) { }); - state = new InitializerState(); - state.init(count[0], sessionsPerSegment); + state = new InitializerState(count[0], sessionsPerSegment); saveStateToCache(state); } return state; diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanCacheInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanCacheInitializer.java index ef45bd9db590..17dc54c08046 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanCacheInitializer.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanCacheInitializer.java @@ -124,9 +124,7 @@ protected void startLoading() { 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 diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java index 6747e40324d0..3efd76bf51f8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InitializerState.java @@ -20,50 +20,70 @@ 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 NOT 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 Marek Posolda */ +@SerializeWith(InitializerState.ExternalizerImpl.class) public class InitializerState extends SessionEntity { private static final Logger log = Logger.getLogger(InitializerState.class); - private int sessionsCount; - private List 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); + log.debugf("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCountLocal); - for (int i=0 ; i getUnfinishedSegments(int segmentCount) { - List result = new ArrayList<>(); + /** Return next un-finished segments. It returns at most {@code maxSegmentCount} segments. */ + public List getUnfinishedSegments(int maxSegmentCount) { + List 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 List getUnfinishedSegments(int segmentCount) { } public void markSegmentFinished(int index) { - segments.set(index, true); + segments.set(index); updateLowestUnfinishedSegment(); } @@ -86,35 +106,55 @@ private void updateLowestUnfinishedSegment() { } private int getNextUnfinishedSegmentFromIndex(int index) { - int segmentsSize = segments.size(); - for (int i=index ; i { - int size = segments.size(); - for (int i=0 ; i entry) { AuthenticationSessionEntity entity = entry.getValue(); - if (!realm.equals(entity.getRealm())) { + if (!realm.equals(entity.getRealmId())) { return false; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/SessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/SessionPredicate.java index f72b92d7ce6c..1820cda77eb4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/SessionPredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/SessionPredicate.java @@ -41,7 +41,7 @@ public static SessionPredicate create(String realm) { @Override public boolean test(Map.Entry> entry) { - return realm.equals(entry.getValue().getEntity().getRealm()); + return realm.equals(entry.getValue().getEntity().getRealmId()); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java index 499600073eaa..ef6155fbdb02 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java @@ -43,7 +43,7 @@ public static UserLoginFailurePredicate create(String realm) { @Override public boolean test(Map.Entry> entry) { LoginFailureEntity e = entry.getValue().getEntity(); - return realm.equals(e.getRealm()); + return realm.equals(e.getRealmId()); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java index 06609f2b6d1e..166ee4d6ce89 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java @@ -83,7 +83,7 @@ public boolean test(Map.Entry> e UserSessionEntity entity = (UserSessionEntity) e; - if (!realm.equals(entity.getRealm())) { + if (!realm.equals(entity.getRealmId())) { return false; } diff --git a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGRemoveSessionTest.java b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGRemoveSessionTest.java index ff4c3ce83eb0..5b7abe06952c 100644 --- a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGRemoveSessionTest.java +++ b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGRemoveSessionTest.java @@ -162,7 +162,7 @@ private static SessionEntityWrapper createSessionEntity(Strin // 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"); diff --git a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java index 5e79226f1b60..ce5ca6b81181 100644 --- a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java +++ b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java @@ -75,7 +75,7 @@ public static void main(String[] args) throws Exception { // 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"); diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheConcurrentWritesTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheConcurrentWritesTest.java index 22b3ac7a9394..a5aae292518c 100644 --- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheConcurrentWritesTest.java +++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheConcurrentWritesTest.java @@ -59,7 +59,7 @@ public static void main(String[] args) throws Exception { // 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 @@ private boolean cacheReplace(SessionEntityWrapper oldSession, 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; } diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheWriteSkewTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheWriteSkewTest.java index 3d0cd53399e4..2ea5245583f0 100644 --- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheWriteSkewTest.java +++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/DistributedCacheWriteSkewTest.java @@ -60,7 +60,7 @@ public static void main(String[] args) throws Exception { // 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"); diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java index 44637953f116..32210a0f02aa 100644 --- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java +++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java @@ -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 segments = state.getUnfinishedSegments(3); diff --git a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java index ba32eaac5f92..9061c96174b5 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java @@ -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); - - } diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java index 23760a137f94..41495d406065 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java @@ -73,7 +73,7 @@ protected void printSession(String id, UserSessionEntity userSession) { 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 @@ protected void doRunCacheCommand(KeycloakSession session, Cache