From 395bd447f2d5e05254d6150cc720874d9452fea4 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Fri, 25 Mar 2022 13:33:30 +0100 Subject: [PATCH] Hot Rod map storage: Login failure no-downtime store --- .../HotRodMapStorageProviderFactory.java | 11 +++ .../hotRod/common/ProtoSchemaInitializer.java | 4 + .../HotRodUserLoginFailureEntity.java | 95 +++++++++++++++++++ .../src/main/resources/config/cacheConfig.xml | 8 ++ .../src/main/resources/config/infinispan.xml | 8 ++ .../resources/META-INF/keycloak-server.json | 2 +- .../integration-arquillian/tests/pom.xml | 1 + .../src/main/resources/hotrod/infinispan.xml | 8 ++ .../model/parameters/HotRodMapStorage.java | 2 +- 9 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java index 8da90de78f..5d70654d98 100644 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java +++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java @@ -27,15 +27,19 @@ import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserModel; import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; import org.keycloak.models.map.clientscope.MapClientScopeEntity; import org.keycloak.models.map.group.MapGroupEntity; +import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; import org.keycloak.models.map.role.MapRoleEntity; import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntityDelegate; import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity; import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntityDelegate; +import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity; +import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntityDelegate; import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity; import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntityDelegate; import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity; @@ -84,6 +88,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory .constructor(MapUserCredentialEntity.class, HotRodUserCredentialEntityDelegate::new) .constructor(MapUserFederatedIdentityEntity.class, HotRodUserFederatedIdentityEntityDelegate::new) .constructor(MapUserConsentEntity.class, HotRodUserConsentEntityDelegate::new) + .constructor(MapUserLoginFailureEntity.class, HotRodUserLoginFailureEntityDelegate::new) .build(); public static final Map, HotRodEntityDescriptor> ENTITY_DESCRIPTOR_MAP = new HashMap<>(); @@ -122,6 +127,12 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory new HotRodEntityDescriptor<>(UserModel.class, HotRodUserEntity.class, HotRodUserEntityDelegate::new)); + + // Login failure descriptor + ENTITY_DESCRIPTOR_MAP.put(UserLoginFailureModel.class, + new HotRodEntityDescriptor<>(UserLoginFailureModel.class, + HotRodUserLoginFailureEntity.class, + HotRodUserLoginFailureEntityDelegate::new)); } @Override diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java index 6c80b704b5..5b87a2c870 100644 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java +++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java @@ -26,6 +26,7 @@ import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity; import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity; import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity; import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity; +import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity; import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity; import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity; import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntity; @@ -61,6 +62,9 @@ import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEn HotRodUserCredentialEntity.class, HotRodUserFederatedIdentityEntity.class, + // Login Failures + HotRodUserLoginFailureEntity.class, + // Common HotRodPair.class, HotRodAttributeEntity.class, diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java new file mode 100644 index 0000000000..9d5ea5a99e --- /dev/null +++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022 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.map.storage.hotRod.loginFailure; + +import org.infinispan.protostream.annotations.ProtoDoc; +import org.infinispan.protostream.annotations.ProtoField; +import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; +import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; +import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; +import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; + +@GenerateHotRodEntityImplementation( + implementInterface = "org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity", + inherits = "org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity.AbstractHotRodUserLoginFailureEntityDelegate" +) +@ProtoDoc("@Indexed") +public class HotRodUserLoginFailureEntity extends AbstractHotRodEntity { + + @ProtoField(number = 1, required = true) + public int entityVersion = 1; + + @ProtoField(number = 2, required = true) + public String id; + + @ProtoDoc("@Field(index = Index.YES, store = Store.YES)") + @ProtoField(number = 3) + public String realmId; + + @ProtoDoc("@Field(index = Index.YES, store = Store.YES)") + @ProtoField(number = 4) + public String userId; + + @ProtoField(number = 5) + public Integer failedLoginNotBefore; + + @ProtoField(number = 6) + public Integer numFailures; + + @ProtoField(number = 7) + public Long lastFailure; + + @ProtoField(number = 8) + public String lastIPFailure; + + public static abstract class AbstractHotRodUserLoginFailureEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapUserLoginFailureEntity { + + @Override + public String getId() { + return getHotRodEntity().id; + } + + @Override + public void setId(String id) { + HotRodUserLoginFailureEntity entity = getHotRodEntity(); + if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); + entity.id = id; + entity.updated |= id != null; + } + + @Override + public void clearFailures() { + HotRodUserLoginFailureEntity entity = getHotRodEntity(); + entity.updated |= getFailedLoginNotBefore() != null || getNumFailures() != null || getLastFailure() != null || getLastIPFailure() != null; + setFailedLoginNotBefore(null); + setNumFailures(null); + setLastFailure(null); + setLastIPFailure(null); + } + } + + @Override + public boolean equals(Object o) { + return HotRodUserLoginFailureEntityDelegate.entityEquals(this, o); + } + + @Override + public int hashCode() { + return HotRodUserLoginFailureEntityDelegate.entityHashCode(this); + } +} diff --git a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml index 45499ec67c..7668cb5325 100644 --- a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml +++ b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml @@ -54,5 +54,13 @@ + + + + kc.HotRodUserLoginFailureEntity + + + + diff --git a/model/map-hot-rod/src/main/resources/config/infinispan.xml b/model/map-hot-rod/src/main/resources/config/infinispan.xml index 10f3b205ff..c1eb5778f4 100644 --- a/model/map-hot-rod/src/main/resources/config/infinispan.xml +++ b/model/map-hot-rod/src/main/resources/config/infinispan.xml @@ -56,5 +56,13 @@ + + + + kc.HotRodUserLoginFailureEntity + + + + diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 228bb66ad0..cef6ea712c 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -266,7 +266,7 @@ "username": "${keycloak.connectionsHotRod.username:myuser}", "password": "${keycloak.connectionsHotRod.password:qwer1234!}", "enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}", - "reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:auth-sessions,clients,client-scopes,groups,users,roles}" + "reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:auth-sessions,clients,client-scopes,groups,users,user-login-failures,roles}" } }, diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 633f37ad52..6f4cc71cc1 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -1502,6 +1502,7 @@ hotrod hotrod hotrod + hotrod diff --git a/testsuite/model/src/main/resources/hotrod/infinispan.xml b/testsuite/model/src/main/resources/hotrod/infinispan.xml index b1cd5bfaaa..fe9ecbd66b 100644 --- a/testsuite/model/src/main/resources/hotrod/infinispan.xml +++ b/testsuite/model/src/main/resources/hotrod/infinispan.xml @@ -52,5 +52,13 @@ + + + + kc.HotRodUserLoginFailureEntity + + + + diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java index c9559650d3..92519e5609 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java @@ -83,7 +83,7 @@ public class HotRodMapStorage extends KeycloakModelParameters { .spi("user").provider(MapUserProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) .spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-user-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) .config("storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) + .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) .spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID); cf.spi(MapStorageSpi.NAME)