Login Failures Map JPA implementation

Closes #9664
This commit is contained in:
Stefan Guilhen 2022-04-06 10:47:03 -03:00 committed by Hynek Mlnařík
parent 53ea60b8d5
commit 0c7a8c8684
13 changed files with 607 additions and 10 deletions

View file

@ -23,4 +23,5 @@ public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1; public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1;
public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1; public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1;
public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1; public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE = 1;
} }

View file

@ -17,7 +17,10 @@
package org.keycloak.models.map.storage.jpa; package org.keycloak.models.map.storage.jpa;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorage; import org.keycloak.models.map.storage.MapStorage;
@ -26,6 +29,8 @@ import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag;
public class JpaMapStorageProvider implements MapStorageProvider { public class JpaMapStorageProvider implements MapStorageProvider {
private static final Logger logger = Logger.getLogger(JpaMapStorageProvider.class);
private final String SESSION_TX_PREFIX = "jpa-map-tx-"; private final String SESSION_TX_PREFIX = "jpa-map-tx-";
private final JpaMapStorageProviderFactory factory; private final JpaMapStorageProviderFactory factory;
@ -46,6 +51,9 @@ public class JpaMapStorageProvider implements MapStorageProvider {
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelType, Flag... flags) { public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelType, Flag... flags) {
if (modelType == UserLoginFailureModel.class) {
logger.warn("Enabling JPA storage for user login failures will result in testsuite failures until GHI #11230 is resolved");
}
factory.validateAndUpdateSchema(session, modelType); factory.validateAndUpdateSchema(session, modelType);
return new MapStorage<V, M>() { return new MapStorage<V, M>() {
@Override @Override

View file

@ -56,6 +56,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.dblock.DBLockProvider; import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.map.client.MapProtocolMapperEntity; import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
@ -94,6 +95,8 @@ import org.keycloak.models.map.storage.jpa.group.JpaGroupMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity; import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener; import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener;
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaOptimisticLockingListener; import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaOptimisticLockingListener;
import org.keycloak.models.map.storage.jpa.loginFailure.JpaUserLoginFailureMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity;
import org.keycloak.models.map.storage.jpa.realm.JpaRealmMapKeycloakTransaction; import org.keycloak.models.map.storage.jpa.realm.JpaRealmMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity; import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity; import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
@ -129,7 +132,7 @@ public class JpaMapStorageProviderFactory implements
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new) .constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
//group //group
.constructor(JpaGroupEntity.class, JpaGroupEntity::new) .constructor(JpaGroupEntity.class, JpaGroupEntity::new)
// realm //realm
.constructor(JpaRealmEntity.class, JpaRealmEntity::new) .constructor(JpaRealmEntity.class, JpaRealmEntity::new)
.constructor(JpaComponentEntity.class, JpaComponentEntity::new) .constructor(JpaComponentEntity.class, JpaComponentEntity::new)
.constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new) .constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new)
@ -144,6 +147,8 @@ public class JpaMapStorageProviderFactory implements
.constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new) .constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new)
//role //role
.constructor(JpaRoleEntity.class, JpaRoleEntity::new) .constructor(JpaRoleEntity.class, JpaRoleEntity::new)
//user login-failure
.constructor(JpaUserLoginFailureEntity.class, JpaUserLoginFailureEntity::new)
.build(); .build();
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>(); private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
@ -154,6 +159,7 @@ public class JpaMapStorageProviderFactory implements
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new); MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new); MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new); MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
} }
public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) { public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {

View file

@ -16,12 +16,13 @@
*/ */
package org.keycloak.models.map.storage.jpa.hibernate.jsonb; package org.keycloak.models.map.storage.jpa.hibernate.jsonb;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionMetadata; import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionMetadata;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionMetadata; import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionMetadata;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata; import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata;
@ -35,6 +36,8 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMig
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRealmMigration; import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRealmMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration; import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration; import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureMetadata;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata; import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata; import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata; import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
@ -45,19 +48,21 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP; import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM; import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE; import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
public class JpaEntityMigration { public class JpaEntityMigration {
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>(); static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
static { static {
MIGRATIONS.put(JpaAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaAuthenticationSessionMigration.MIGRATORS)); MIGRATIONS.put(JpaAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaAuthenticationSessionMigration.MIGRATORS));
MIGRATIONS.put(JpaRootAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaRootAuthenticationSessionMigration.MIGRATORS)); MIGRATIONS.put(JpaRootAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaRootAuthenticationSessionMigration.MIGRATORS));
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS)); MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS)); MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
MIGRATIONS.put(JpaComponentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaComponentMigration.MIGRATORS)); MIGRATIONS.put(JpaComponentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaComponentMigration.MIGRATORS));
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS)); MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS)); MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS)); MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
} }
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) { private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {

View file

@ -0,0 +1,35 @@
/*
* 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.jpa.hibernate.jsonb.migration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Migration functions for user login failures.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaUserLoginFailureMigration {
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
o -> o // no migration yet
);
}

View file

@ -0,0 +1,72 @@
/*
* 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.jpa.loginFailure;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityDelegate;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
import org.keycloak.models.map.storage.jpa.loginFailure.delegate.JpaUserLoginFailureDelegateProvider;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
/**
* A {@link JpaMapKeycloakTransaction} implementation for user login failure entities.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaUserLoginFailureMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaUserLoginFailureEntity, MapUserLoginFailureEntity, UserLoginFailureModel> {
@SuppressWarnings("unchecked")
public JpaUserLoginFailureMapKeycloakTransaction(EntityManager em) {
super(JpaUserLoginFailureEntity.class, em);
}
@Override
public Selection<JpaUserLoginFailureEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaUserLoginFailureEntity> root) {
return cb.construct(JpaUserLoginFailureEntity.class,
root.get("id"),
root.get("version"),
root.get("entityVersion"),
root.get("realmId"),
root.get("userId")
);
}
@Override
public void setEntityVersion(JpaRootEntity entity) {
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE);
}
@Override
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
return new JpaUserLoginFailureModelCriteriaBuilder();
}
@Override
protected MapUserLoginFailureEntity mapToEntityDelegate(JpaUserLoginFailureEntity original) {
return new MapUserLoginFailureEntityDelegate(new JpaUserLoginFailureDelegateProvider(original, em));
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.jpa.loginFailure;
import java.util.function.BiFunction;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity;
import org.keycloak.storage.SearchableModelField;
/**
* A {@link JpaModelCriteriaBuilder} implementation for user login failures.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaUserLoginFailureModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaUserLoginFailureEntity, UserLoginFailureModel, JpaUserLoginFailureModelCriteriaBuilder> {
public JpaUserLoginFailureModelCriteriaBuilder() {
super(JpaUserLoginFailureModelCriteriaBuilder::new);
}
private JpaUserLoginFailureModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaUserLoginFailureEntity>, Predicate> predicateFunc) {
super(JpaUserLoginFailureModelCriteriaBuilder::new, predicateFunc);
}
@Override
public JpaUserLoginFailureModelCriteriaBuilder compare(SearchableModelField<? super UserLoginFailureModel> modelField, Operator op, Object... value) {
switch (op) {
case EQ:
if (modelField.equals(UserLoginFailureModel.SearchableFields.REALM_ID) ||
modelField.equals(UserLoginFailureModel.SearchableFields.USER_ID)) {
validateValue(value, modelField, op, String.class);
return new JpaUserLoginFailureModelCriteriaBuilder((cb, root) ->
cb.equal(root.get(modelField.getName()), value[0])
);
} else {
throw new CriterionNotSupportedException(modelField, op);
}
default:
throw new CriterionNotSupportedException(modelField, op);
}
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.jpa.loginFailure.delegate;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.delegate.DelegateProvider;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityFields;
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity;
/**
* A {@link DelegateProvider} implementation for {@link JpaUserLoginFailureEntity}.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaUserLoginFailureDelegateProvider extends JpaDelegateProvider<JpaUserLoginFailureEntity> implements DelegateProvider<MapUserLoginFailureEntity> {
private final EntityManager em;
public JpaUserLoginFailureDelegateProvider(JpaUserLoginFailureEntity delegate, EntityManager em) {
super(delegate);
this.em = em;
}
@Override
public MapUserLoginFailureEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapUserLoginFailureEntity>> field, Object... parameters) {
if (getDelegate().isMetadataInitialized()) return getDelegate();
if (isRead) {
if (field instanceof MapUserLoginFailureEntityFields) {
switch ((MapUserLoginFailureEntityFields) field) {
case ID:
case REALM_ID:
case USER_ID:
return getDelegate();
default:
setDelegate(em.find(JpaUserLoginFailureEntity.class, UUID.fromString(getDelegate().getId())));
}
} else {
throw new IllegalStateException("Not a valid user login failure field: " + field);
}
} else {
setDelegate(em.find(JpaUserLoginFailureEntity.class, UUID.fromString(getDelegate().getId())));
}
return getDelegate();
}
}

View file

@ -0,0 +1,216 @@
/*
* 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.jpa.loginFailure.entity;
import java.util.Objects;
import java.util.UUID;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UuidValidator;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
/**
* JPA {@link MapUserLoginFailureEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)}
* to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
@Entity
@Table(name = "kc_user_login_failure",
uniqueConstraints = {
@UniqueConstraint(
columnNames = {"realmId", "userId"}
)
})
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaUserLoginFailureEntity extends MapUserLoginFailureEntity.AbstractUserLoginFailureEntity implements JpaRootVersionedEntity {
@Id
@Column
private UUID id;
//used for implicit optimistic locking
@Version
@Column
private int version;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private final JpaUserLoginFailureMetadata metadata;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Integer entityVersion;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String realmId;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String userId;
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaUserLoginFailureEntity() {
this.metadata = new JpaUserLoginFailureMetadata();
}
public JpaUserLoginFailureEntity(DeepCloner cloner) {
this.metadata = new JpaUserLoginFailureMetadata(cloner);
}
/**
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
* It is used to select user login failure without metadata(json) field.
*/
public JpaUserLoginFailureEntity(UUID id, int version, Integer entityVersion, String realmId, String userId) {
this.id = id;
this.version = version;
this.entityVersion = entityVersion;
this.realmId = realmId;
this.userId = userId;
this.metadata = null;
}
public boolean isMetadataInitialized() {
return this.metadata != null;
}
@Override
public Integer getEntityVersion() {
if (isMetadataInitialized()) return metadata.getEntityVersion();
return this.entityVersion;
}
@Override
public void setEntityVersion(Integer entityVersion) {
this.metadata.setEntityVersion(entityVersion);
}
@Override
public Integer getCurrentSchemaVersion() {
return CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
}
@Override
public int getVersion() {
return this.version;
}
@Override
public String getId() {
return this.id == null ? null : this.id.toString();
}
@Override
public void setId(String id) {
String validatedId = UuidValidator.validateAndConvert(id);
this.id = UUID.fromString(validatedId);
}
@Override
public String getRealmId() {
if (isMetadataInitialized()) return this.metadata.getRealmId();
return this.realmId;
}
@Override
public void setRealmId(String realmId) {
this.metadata.setRealmId(realmId);
}
@Override
public String getUserId() {
if (isMetadataInitialized()) return this.metadata.getUserId();
return this.userId;
}
@Override
public void setUserId(String userId) {
this.metadata.setUserId(userId);
}
@Override
public Long getFailedLoginNotBefore() {
return this.metadata.getFailedLoginNotBefore();
}
@Override
public void setFailedLoginNotBefore(Long failedLoginNotBefore) {
this.metadata.setFailedLoginNotBefore(failedLoginNotBefore);
}
@Override
public Integer getNumFailures() {
return this.metadata.getNumFailures();
}
@Override
public void setNumFailures(Integer numFailures) {
this.metadata.setNumFailures(numFailures);
}
@Override
public Long getLastFailure() {
return this.metadata.getLastFailure();
}
@Override
public void setLastFailure(Long lastFailure) {
this.metadata.setLastFailure(lastFailure);
}
@Override
public String getLastIPFailure() {
return this.metadata.getLastIPFailure();
}
@Override
public void setLastIPFailure(String lastIPFailure) {
this.metadata.setLastIPFailure(lastIPFailure);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaUserLoginFailureEntity)) return false;
return Objects.equals(getId(), ((JpaUserLoginFailureEntity) obj).getId());
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.jpa.loginFailure.entity;
import java.io.Serializable;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityImpl;
/**
* Class that contains all the user login failure metadata that is written as JSON into the database.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaUserLoginFailureMetadata extends MapUserLoginFailureEntityImpl implements Serializable {
public JpaUserLoginFailureMetadata(DeepCloner cloner) {
super(cloner);
}
public JpaUserLoginFailureMetadata() {
super();
}
private Integer entityVersion;
public Integer getEntityVersion() {
return entityVersion;
}
public void setEntityVersion(Integer entityVersion) {
this.entityVersion = entityVersion;
}
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<!-- format of id of changelog file names: jpa-user-login-failures-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE}.xml -->
<include file="META-INF/user-login-failures/jpa-user-login-failures-changelog-1.xml"/>
</databaseChangeLog>

View file

@ -20,5 +20,7 @@
<!--roles--> <!--roles-->
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class> <class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class> <class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>
<!--user-login-failures-->
<class>org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity</class>
</persistence-unit> </persistence-unit>
</persistence> </persistence>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!-- format of id of changeSet: user-login-failures-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE} -->
<changeSet author="keycloak" id="user-login-failures-1">
<createTable tableName="kc_user_login_failure">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="version" type="INTEGER" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<ext:addGeneratedColumn tableName="kc_user_login_failure">
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
<ext:column name="realmid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fRealmId"/>
<ext:column name="userid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fUserId"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_user_login_failure" indexName="user_login_failure_entityVersion">
<column name="entityversion"/>
</createIndex>
<createIndex tableName="kc_user_login_failure" indexName="user_login_failure_realmId_userId" unique="true">
<column name="realmid"/>
<column name="userid"/>
</createIndex>
</changeSet>
</databaseChangeLog>