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 2bced7e451..0000000000 --- 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 d7bdcdfcce..e56d4a4043 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 class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent { 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 d658f30c58..0000000000 --- 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 05a762b54f..5736188623 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 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(); 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 2a31453a94..8fd6f54215 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 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); - } } 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 e4f3bd0c08..63882dc80c 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 @@ 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 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 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 a2b8a873ff..5ae841632d 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 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()); 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 f9e21e18de..8b6df90c3d 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 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 createLoginFailureTask = new SessionUpdateTask() { @@ -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()); 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 195099258d..694c41e461 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 class InfinispanChangelogBasedTransaction 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 ext 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 class InfinispanChangelogBasedTransaction 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); 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 39cbdae1a0..3d7d9af3d5 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 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()); } } 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 5b328ecc91..49f0fa6415 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 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 { + + 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 318b1ba547..484aec71fe 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 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 { + + 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 37f8e081ce..6d93e19d39 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 @@ 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) { 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 8ac85a17e4..78df451b60 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.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 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 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 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 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 cca28cc656..40065d5b55 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 abstract class BaseCacheInitializer extends CacheInitializer { }); - 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 ef45bd9db5..17dc54c080 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 @@ 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 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 6747e40324..3efd76bf51 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 @@ 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 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); - - for (int i=0 ; i getUnfinishedSegments(int segmentCount) { - List 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 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 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 { + + 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(); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/AuthenticationSessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/AuthenticationSessionPredicate.java index c471793842..32bf3a6939 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/AuthenticationSessionPredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/AuthenticationSessionPredicate.java @@ -76,7 +76,7 @@ public class AuthenticationSessionPredicate implements Predicate 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 f72b92d7ce..1820cda77e 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 class SessionPredicate implements Predicate> 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 499600073e..ef6155fbdb 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 class UserLoginFailurePredicate implements Predicate> 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 06609f2b6d..166ee4d6ce 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 class UserSessionPredicate implements Predicate(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 3d0cd53399..2ea5245583 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 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"); 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 44637953f1..32210a0f02 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 ba32eaac5f..9061c96174 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 23760a137f..41495d4060 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 @@ 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));