Users Map JPA implementation (#12871)
This commit is contained in:
parent
b5ca03222f
commit
dc88dd5286
28 changed files with 1851 additions and 61 deletions
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
|
||||
public interface Constants {
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_ADMIN_EVENT = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_AUTH_EVENT = 1;
|
||||
|
@ -34,4 +36,8 @@ public interface Constants {
|
|||
public static final Integer CURRENT_SCHEMA_VERSION_ROOT_AUTH_SESSION = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_USER = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_USER_CONSENT = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY = 1;
|
||||
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserLoginFailureModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.dblock.DBLockProvider;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
|
||||
|
@ -130,6 +131,12 @@ import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
|||
import org.keycloak.models.map.storage.jpa.singleUseObject.JpaSingleUseObjectMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
|
||||
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
|
||||
import org.keycloak.models.map.storage.jpa.user.JpaUserMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntityImpl;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
|
@ -153,26 +160,26 @@ public class JpaMapStorageProviderFactory implements
|
|||
private final String sessionTxKey;
|
||||
|
||||
public final static DeepCloner CLONER = new DeepCloner.Builder()
|
||||
//auth-session
|
||||
//auth-sessions
|
||||
.constructor(JpaRootAuthenticationSessionEntity.class, JpaRootAuthenticationSessionEntity::new)
|
||||
.constructor(JpaAuthenticationSessionEntity.class, JpaAuthenticationSessionEntity::new)
|
||||
//authz
|
||||
//authorization
|
||||
.constructor(JpaResourceServerEntity.class, JpaResourceServerEntity::new)
|
||||
.constructor(JpaResourceEntity.class, JpaResourceEntity::new)
|
||||
.constructor(JpaScopeEntity.class, JpaScopeEntity::new)
|
||||
.constructor(JpaPermissionEntity.class, JpaPermissionEntity::new)
|
||||
.constructor(JpaPolicyEntity.class, JpaPolicyEntity::new)
|
||||
//client
|
||||
//clients
|
||||
.constructor(JpaClientEntity.class, JpaClientEntity::new)
|
||||
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
|
||||
//client-scope
|
||||
//client-scopes
|
||||
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
|
||||
//event
|
||||
//events
|
||||
.constructor(JpaAdminEventEntity.class, JpaAdminEventEntity::new)
|
||||
.constructor(JpaAuthEventEntity.class, JpaAuthEventEntity::new)
|
||||
//group
|
||||
//groups
|
||||
.constructor(JpaGroupEntity.class, JpaGroupEntity::new)
|
||||
//realm
|
||||
//realms
|
||||
.constructor(JpaRealmEntity.class, JpaRealmEntity::new)
|
||||
.constructor(JpaComponentEntity.class, JpaComponentEntity::new)
|
||||
.constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new)
|
||||
|
@ -185,34 +192,48 @@ public class JpaMapStorageProviderFactory implements
|
|||
.constructor(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl::new)
|
||||
.constructor(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl::new)
|
||||
.constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new)
|
||||
//role
|
||||
//roles
|
||||
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
||||
//single-use-object
|
||||
//single-use-objects
|
||||
.constructor(JpaSingleUseObjectEntity.class, JpaSingleUseObjectEntity::new)
|
||||
//user-login-failure
|
||||
//user-login-failures
|
||||
.constructor(JpaUserLoginFailureEntity.class, JpaUserLoginFailureEntity::new)
|
||||
//users
|
||||
.constructor(JpaUserEntity.class, JpaUserEntity::new)
|
||||
.constructor(JpaUserConsentEntity.class, JpaUserConsentEntity::new)
|
||||
.constructor(JpaUserFederatedIdentityEntity.class, JpaUserFederatedIdentityEntity::new)
|
||||
.constructor(MapUserCredentialEntity.class, MapUserCredentialEntityImpl::new)
|
||||
.build();
|
||||
|
||||
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
|
||||
static {
|
||||
//auth-sessions
|
||||
MODEL_TO_TX.put(RootAuthenticationSessionModel.class, JpaRootAuthenticationSessionMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
||||
//event
|
||||
MODEL_TO_TX.put(AdminEvent.class, JpaAdminEventMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(Event.class, JpaAuthEventMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(ActionTokenValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
|
||||
|
||||
//authz
|
||||
//authorization
|
||||
MODEL_TO_TX.put(ResourceServer.class, JpaResourceServerMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(Resource.class, JpaResourceMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(Scope.class, JpaScopeMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(PermissionTicket.class, JpaPermissionMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(Policy.class, JpaPolicyMapKeycloakTransaction::new);
|
||||
//clients
|
||||
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
||||
//client-scopes
|
||||
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
|
||||
//events
|
||||
MODEL_TO_TX.put(AdminEvent.class, JpaAdminEventMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(Event.class, JpaAuthEventMapKeycloakTransaction::new);
|
||||
//groups
|
||||
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
|
||||
//realms
|
||||
MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
|
||||
//roles
|
||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||
//single-use-objects
|
||||
MODEL_TO_TX.put(ActionTokenValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
|
||||
//user-login-failures
|
||||
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
|
||||
//users
|
||||
MODEL_TO_TX.put(UserModel.class, JpaUserMapKeycloakTransaction::new);
|
||||
}
|
||||
|
||||
public JpaMapStorageProviderFactory() {
|
||||
|
|
|
@ -51,12 +51,18 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigr
|
|||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaScopeMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaSingleUseObjectMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserConsentMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserFederatedIdentityMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserMigration;
|
||||
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.JpaRealmMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserMetadata;
|
||||
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION;
|
||||
|
@ -72,33 +78,47 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
|
|||
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_SINGLE_USE_OBJECT;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_CONSENT;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
|
||||
|
||||
public class JpaEntityMigration {
|
||||
|
||||
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
|
||||
static {
|
||||
//auth-sessions
|
||||
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(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(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(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(JpaSingleUseObjectMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT, tree, JpaSingleUseObjectMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
|
||||
|
||||
//authz
|
||||
//authorization
|
||||
MIGRATIONS.put(JpaPermissionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION, tree, JpaPermissionMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaPolicyMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_POLICY, tree, JpaPolicyMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaResourceMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE, tree, JpaResourceMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaResourceServerMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER, tree, JpaResourceServerMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE, tree, JpaScopeMigration.MIGRATORS));
|
||||
|
||||
// events
|
||||
//clients
|
||||
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
||||
//client-scopes
|
||||
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
|
||||
//events
|
||||
MIGRATIONS.put(JpaAdminEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ADMIN_EVENT, tree, JpaAdminEventMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaAuthEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_EVENT, tree, JpaAuthEventMigration.MIGRATORS));
|
||||
//groups
|
||||
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
|
||||
//realms
|
||||
MIGRATIONS.put(JpaComponentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaComponentMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
|
||||
//roles
|
||||
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||
//single-use-objects
|
||||
MIGRATIONS.put(JpaSingleUseObjectMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT, tree, JpaSingleUseObjectMigration.MIGRATORS));
|
||||
//user-login-failures
|
||||
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
|
||||
//users
|
||||
MIGRATIONS.put(JpaUserMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER, tree, JpaUserMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaUserConsentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_CONSENT, tree, JpaUserConsentMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaUserFederatedIdentityMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY, tree, JpaUserFederatedIdentityMigration.MIGRATORS));
|
||||
|
||||
}
|
||||
|
||||
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {
|
||||
|
|
|
@ -78,6 +78,8 @@ import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
|
|||
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntityImpl;
|
||||
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
|
||||
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntityImpl;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntityImpl;
|
||||
import org.keycloak.util.EnumWithStableIndex;
|
||||
|
||||
public class JsonbType extends AbstractSingleColumnStandardBasicType<Object> implements DynamicParameterizedType {
|
||||
|
@ -102,7 +104,9 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType<Object> imp
|
|||
.addAbstractTypeMapping(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl.class)
|
||||
.addAbstractTypeMapping(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl.class)
|
||||
.addAbstractTypeMapping(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl.class)
|
||||
.addAbstractTypeMapping(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl.class))
|
||||
.addAbstractTypeMapping(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl.class)
|
||||
// user abstract type mappings
|
||||
.addAbstractTypeMapping(MapUserCredentialEntity.class, MapUserCredentialEntityImpl.class))
|
||||
.addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class)
|
||||
.addMixIn(DeepCloner.class, IgnoredTypeMixIn.class)
|
||||
.addMixIn(EntityWithAttributes.class, IgnoredMetadataFieldsMixIn.class)
|
||||
|
|
|
@ -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 consents.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserConsentMigration {
|
||||
|
||||
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||
o -> o // no migration yet
|
||||
);
|
||||
}
|
|
@ -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 federated identities.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserFederatedIdentityMigration {
|
||||
|
||||
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||
o -> o // no migration yet
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Migration functions for users.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserMigration {
|
||||
|
||||
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||
o -> o // no migration yet
|
||||
);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.user;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
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.user.delegate.JpaUserDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntityDelegate;
|
||||
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER;
|
||||
|
||||
/**
|
||||
* A {@link org.keycloak.models.map.storage.MapKeycloakTransaction} implementation for user entities.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaUserEntity, MapUserEntity, UserModel> {
|
||||
|
||||
public JpaUserMapKeycloakTransaction(final EntityManager em) {
|
||||
super(JpaUserEntity.class, UserModel.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Selection<? extends JpaUserEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaUserEntity> root) {
|
||||
return cb.construct(JpaUserEntity.class,
|
||||
root.get("id"),
|
||||
root.get("version"),
|
||||
root.get("entityVersion"),
|
||||
root.get("realmId"),
|
||||
root.get("username"),
|
||||
root.get("firstName"),
|
||||
root.get("lastName"),
|
||||
root.get("email"),
|
||||
root.get("emailConstraint"),
|
||||
root.get("federationLink"),
|
||||
root.get("enabled"),
|
||||
root.get("emailVerified"),
|
||||
root.get("timestamp")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEntityVersion(JpaRootEntity entity) {
|
||||
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||
return new JpaUserModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapUserEntity mapToEntityDelegate(JpaUserEntity original) {
|
||||
return new MapUserEntityDelegate(new JpaUserDelegateProvider(original, this.em));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.user;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
||||
/**
|
||||
* A {@link JpaModelCriteriaBuilder} implementation for users.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaUserEntity, UserModel, JpaUserModelCriteriaBuilder> {
|
||||
|
||||
public JpaUserModelCriteriaBuilder() {
|
||||
super(JpaUserModelCriteriaBuilder::new);
|
||||
}
|
||||
|
||||
private JpaUserModelCriteriaBuilder(final JpaPredicateFunction<JpaUserEntity> predicateFunc) {
|
||||
super(JpaUserModelCriteriaBuilder::new, predicateFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaUserModelCriteriaBuilder compare(SearchableModelField<? super UserModel> modelField, Operator op, Object... value) {
|
||||
switch(op) {
|
||||
case EQ:
|
||||
if (modelField == UserModel.SearchableFields.REALM_ID ||
|
||||
modelField == UserModel.SearchableFields.USERNAME ||
|
||||
modelField == UserModel.SearchableFields.EMAIL ||
|
||||
modelField == UserModel.SearchableFields.FEDERATION_LINK) {
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.get(modelField.getName()), value[0])
|
||||
);
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.ENABLED ||
|
||||
modelField == UserModel.SearchableFields.EMAIL_VERIFIED) {
|
||||
|
||||
validateValue(value, modelField, op, Boolean.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.get(modelField.getName()), value[0])
|
||||
);
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.IDP_AND_USER) {
|
||||
|
||||
if (value == null || value.length == 0 || value.length > 2) {
|
||||
throw new CriterionNotSupportedException(modelField, op,
|
||||
"Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(value));
|
||||
}
|
||||
|
||||
if (value.length == 1) {
|
||||
// search by idp only
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.join("federatedIdentities", JoinType.LEFT).get("identityProvider"),
|
||||
value[0]));
|
||||
} else if (value[0] == null) {
|
||||
// search by userid only
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.join("federatedIdentities", JoinType.LEFT).get("userId"),
|
||||
value[1]));
|
||||
} else {
|
||||
// search using both idp and userid
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) -> {
|
||||
Join<JpaUserEntity, JpaUserFederatedIdentityEntity> join =
|
||||
root.join("federatedIdentities", JoinType.LEFT);
|
||||
return cb.and(cb.equal(join.get("identityProvider"), value[0]),
|
||||
cb.equal(join.get("userId"),value[1]));
|
||||
});
|
||||
}
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.ASSIGNED_GROUP) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.join("groupIds", JoinType.LEFT), value[0]));
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.ASSIGNED_ROLE) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.join("roleIds", JoinType.LEFT), value[0]));
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(
|
||||
cb.function("->>", String.class, root.get("metadata"), cb.literal("fServiceAccountClientLink")),
|
||||
value[0]));
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.CONSENT_FOR_CLIENT) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.join("consents", JoinType.LEFT).get("clientId"), value[0]));
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) -> {
|
||||
String providerId = new StorageId((String) value[0], "").getId() + "%";
|
||||
return cb.like(root.join("consents", JoinType.LEFT).get("clientId"), providerId);
|
||||
});
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) -> {
|
||||
Join<JpaUserEntity, JpaUserConsentEntity> join = root.join("consents", JoinType.LEFT);
|
||||
return cb.isTrue(cb.function("@>",
|
||||
Boolean.TYPE,
|
||||
cb.function("->", JsonbType.class, join.get("metadata"), cb.literal("fGrantedClientScopesIds")),
|
||||
cb.literal(convertToJson(value[0]))));
|
||||
});
|
||||
|
||||
} else if (modelField == UserModel.SearchableFields.ATTRIBUTE) {
|
||||
validateValue(value, modelField, op, String.class, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) -> {
|
||||
Join<JpaUserEntity, JpaUserAttributeEntity> join = root.join("attributes", JoinType.LEFT);
|
||||
return cb.and(
|
||||
cb.equal(join.get("name"), value[0]),
|
||||
cb.equal(join.get("value"), value[1])
|
||||
);
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
case ILIKE:
|
||||
if (modelField == UserModel.SearchableFields.USERNAME ||
|
||||
modelField == UserModel.SearchableFields.FIRST_NAME ||
|
||||
modelField == UserModel.SearchableFields.LAST_NAME ||
|
||||
modelField == UserModel.SearchableFields.EMAIL) {
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()));
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
case IN:
|
||||
if (modelField == UserModel.SearchableFields.ASSIGNED_GROUP) {
|
||||
final Collection<?> collectionValues = getValuesForInOperator(value, modelField);
|
||||
if (collectionValues.isEmpty()) return new JpaUserModelCriteriaBuilder((cb, query, root) -> cb.or());
|
||||
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) -> {
|
||||
CriteriaBuilder.In<String> in = cb.in(root.join("groupIds", JoinType.LEFT));
|
||||
collectionValues.forEach(groupId -> {
|
||||
if (!(groupId instanceof String))
|
||||
throw new CriterionNotSupportedException(modelField, op, "Invalid type. Expected String, got " + groupId.getClass());
|
||||
in.value(groupId.toString());
|
||||
});
|
||||
return in;
|
||||
});
|
||||
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
case NOT_EXISTS:
|
||||
if (modelField == UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT) {
|
||||
return new JpaUserModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.isNull(cb.function("->>", String.class, root.get("metadata"), cb.literal("fServiceAccountClientLink"))));
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
default:
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.user.delegate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.keycloak.models.map.common.EntityField;
|
||||
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntityFields;
|
||||
|
||||
/**
|
||||
* A {@link DelegateProvider} implementation for {@link JpaUserEntity}.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserDelegateProvider extends JpaDelegateProvider<JpaUserEntity> implements DelegateProvider<MapUserEntity> {
|
||||
|
||||
private final EntityManager em;
|
||||
|
||||
public JpaUserDelegateProvider(final JpaUserEntity delegate, final EntityManager em) {
|
||||
super(delegate);
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapUserEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapUserEntity>> field, Object... parameters) {
|
||||
if (getDelegate().isMetadataInitialized()) return getDelegate();
|
||||
if (isRead) {
|
||||
if (field instanceof MapUserEntityFields) {
|
||||
switch ((MapUserEntityFields) field) {
|
||||
case ID:
|
||||
case REALM_ID:
|
||||
case USERNAME:
|
||||
case FIRST_NAME:
|
||||
case LAST_NAME:
|
||||
case EMAIL:
|
||||
case EMAIL_CONSTRAINT:
|
||||
case FEDERATION_LINK:
|
||||
case ENABLED:
|
||||
case EMAIL_VERIFIED:
|
||||
case CREATED_TIMESTAMP:
|
||||
return getDelegate();
|
||||
|
||||
case ATTRIBUTES:
|
||||
this.setDelegateWithAssociation("attributes");
|
||||
break;
|
||||
|
||||
case USER_CONSENTS:
|
||||
this.setDelegateWithAssociation("consents");
|
||||
break;
|
||||
|
||||
case FEDERATED_IDENTITIES:
|
||||
this.setDelegateWithAssociation("federatedIdentities");
|
||||
break;
|
||||
|
||||
default:
|
||||
setDelegate(em.find(JpaUserEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Not a valid realm field: " + field);
|
||||
}
|
||||
} else {
|
||||
setDelegate(em.find(JpaUserEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
return getDelegate();
|
||||
}
|
||||
|
||||
protected void setDelegateWithAssociation(final String associationName) {
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<JpaUserEntity> query = cb.createQuery(JpaUserEntity.class);
|
||||
Root<JpaUserEntity> root = query.from(JpaUserEntity.class);
|
||||
root.fetch(associationName, JoinType.LEFT);
|
||||
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId())));
|
||||
setDelegate(em.createQuery(query).getSingleResult());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.user.entity;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
|
||||
|
||||
/**
|
||||
* JPA implementation for user attributes. This entity represents a user attribute and has a many-to-one relationship
|
||||
* with the user entity.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "kc_user_attribute", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"fk_root", "name", "value"})
|
||||
})
|
||||
public class JpaUserAttributeEntity extends JpaAttributeEntity<JpaUserEntity> {
|
||||
|
||||
public JpaUserAttributeEntity() {
|
||||
}
|
||||
|
||||
public JpaUserAttributeEntity(final JpaUserEntity root, final String name, final String value) {
|
||||
super(root, name, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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.user.entity;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
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.UpdatableEntity;
|
||||
import org.keycloak.models.map.storage.jpa.JpaChildEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
import org.keycloak.models.map.user.MapUserConsentEntity;
|
||||
|
||||
/**
|
||||
* JPA {@link MapUserConsentEntity} 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_consent",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"clientId"})
|
||||
})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaUserConsentEntity extends UpdatableEntity.Impl implements MapUserConsentEntity, JpaChildEntity<JpaUserEntity> {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@GeneratedValue
|
||||
private UUID id;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String clientId;
|
||||
|
||||
@Type(type = "jsonb")
|
||||
@Column(columnDefinition = "jsonb")
|
||||
private final JpaUserConsentMetadata metadata;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name="fk_root")
|
||||
private JpaUserEntity root;
|
||||
|
||||
public JpaUserConsentEntity() {
|
||||
this.metadata = new JpaUserConsentMetadata();
|
||||
}
|
||||
|
||||
public JpaUserConsentEntity(final DeepCloner cloner) {
|
||||
this.metadata = new JpaUserConsentMetadata(cloner);
|
||||
}
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return this.metadata.getEntityVersion();
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer version) {
|
||||
this.metadata.setEntityVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaUserEntity getParent() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
public void setParent(final JpaUserEntity root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.metadata.getClientId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.metadata.setClientId(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getGrantedClientScopesIds() {
|
||||
return this.metadata.getGrantedClientScopesIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGrantedClientScopesId(String scope) {
|
||||
this.metadata.addGrantedClientScopesId(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGrantedClientScopesIds(Set<String> scopesIds) {
|
||||
this.metadata.setGrantedClientScopesIds(scopesIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGrantedClientScopesId(String scopes) {
|
||||
this.metadata.removeGrantedClientScopesId(scopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedDate() {
|
||||
return this.metadata.getCreatedDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedDate(Long createdDate) {
|
||||
this.metadata.setCreatedDate(createdDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLastUpdatedDate() {
|
||||
return this.metadata.getLastUpdatedDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastUpdatedDate(Long lastUpdatedDate) {
|
||||
this.metadata.setLastUpdatedDate(lastUpdatedDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaUserConsentEntity)) return false;
|
||||
return Objects.equals(id, ((JpaUserConsentEntity) obj).id);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.user.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.user.MapUserConsentEntityImpl;
|
||||
|
||||
/**
|
||||
* Class that contains all the user consent metadata that is written as JSON into the database.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserConsentMetadata extends MapUserConsentEntityImpl implements Serializable {
|
||||
|
||||
public JpaUserConsentMetadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JpaUserConsentMetadata(final DeepCloner cloner) {
|
||||
super(cloner);
|
||||
}
|
||||
|
||||
private Integer entityVersion;
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.entityVersion = entityVersion;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,551 @@
|
|||
/*
|
||||
* 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.user.entity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
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.storage.jpa.JpaRootVersionedEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
import org.keycloak.models.map.user.MapUserConsentEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
||||
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER;
|
||||
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||
|
||||
/**
|
||||
* JPA {@link MapUserEntity} 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",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"realmId", "username"}),
|
||||
@UniqueConstraint(columnNames = {"realmId", "emailConstraint"})
|
||||
})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class JpaUserEntity extends MapUserEntity.AbstractUserEntity 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 JpaUserMetadata 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 username;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String firstName;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String lastName;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String email;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String emailConstraint;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String federationLink;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private Boolean enabled;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private Boolean emailVerified;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private Long timestamp;
|
||||
|
||||
@Column(name = "group_id")
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "kc_user_group", joinColumns = @JoinColumn(name = "user_id", nullable = false))
|
||||
private final Set<String> groupIds = new HashSet<>();
|
||||
|
||||
@Column(name = "role_id")
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "kc_user_role", joinColumns = @JoinColumn(name = "user_id", nullable = false))
|
||||
private final Set<String> roleIds = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||
private final Set<JpaUserAttributeEntity> attributes = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||
private final Set<JpaUserConsentEntity> consents = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||
private final Set<JpaUserFederatedIdentityEntity> federatedIdentities = new HashSet<>();
|
||||
|
||||
/**
|
||||
* No-argument constructor, used by hibernate to instantiate entities.
|
||||
*/
|
||||
public JpaUserEntity() {
|
||||
this.metadata = new JpaUserMetadata();
|
||||
}
|
||||
|
||||
public JpaUserEntity(final DeepCloner cloner) {
|
||||
this.metadata = new JpaUserMetadata(cloner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||
* It is used to select user without metadata(json) field.
|
||||
*/
|
||||
public JpaUserEntity(final UUID id, final int version, final Integer entityVersion, final String realmId, final String username,
|
||||
final String firstName, final String lastName, final String email, final String emailConstraint,
|
||||
final String federationLink, final Boolean enabled, final Boolean emailVerified, final Long timestamp) {
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.entityVersion = entityVersion;
|
||||
this.realmId = realmId;
|
||||
this.username = username;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.emailConstraint = emailConstraint;
|
||||
this.federationLink = federationLink;
|
||||
this.enabled = enabled;
|
||||
this.emailVerified = emailVerified;
|
||||
this.timestamp = timestamp;
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
public boolean isMetadataInitialized() {
|
||||
return this.metadata != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEntityVersion() {
|
||||
if (isMetadataInitialized()) return this.metadata.getEntityVersion();
|
||||
return this.entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.metadata.setEntityVersion(entityVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCurrentSchemaVersion() {
|
||||
return CURRENT_SCHEMA_VERSION_USER;
|
||||
}
|
||||
|
||||
@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 (this.isMetadataInitialized()) return this.metadata.getRealmId();
|
||||
return this.realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmId(String realmId) {
|
||||
this.metadata.setRealmId(realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getUsername();
|
||||
return this.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
this.metadata.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getFirstName();
|
||||
return this.firstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
this.metadata.setFirstName(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getCreatedTimestamp();
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedTimestamp(Long createdTimestamp) {
|
||||
this.metadata.setCreatedTimestamp(createdTimestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastName() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getLastName();
|
||||
return this.lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
this.metadata.setLastName(lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getEmail();
|
||||
return this.email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
this.metadata.setEmail(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isEnabled() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.isEnabled();
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.metadata.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isEmailVerified() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.isEmailVerified();
|
||||
return this.emailVerified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmailVerified(Boolean emailVerified) {
|
||||
this.metadata.setEmailVerified(emailVerified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmailConstraint() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getEmailConstraint();
|
||||
return this.emailConstraint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmailConstraint(String emailConstraint) {
|
||||
this.metadata.setEmailConstraint(emailConstraint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFederationLink() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getFederationLink();
|
||||
return this.federationLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFederationLink(String federationLink) {
|
||||
this.metadata.setFederationLink(federationLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return this.metadata.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.metadata.setServiceAccountClientLink(serviceAccountClientLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getNotBefore() {
|
||||
return this.metadata.getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(Long notBefore) {
|
||||
this.metadata.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
//groups membership
|
||||
@Override
|
||||
public Set<String> getGroupsMembership() {
|
||||
return this.groupIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupsMembership(Set<String> groupsMembership) {
|
||||
this.groupIds.clear();
|
||||
if (groupsMembership != null) this.groupIds.addAll(groupsMembership);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGroupsMembership(String groupId) {
|
||||
this.groupIds.add(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroupsMembership(String groupId) {
|
||||
this.groupIds.remove(groupId);
|
||||
}
|
||||
|
||||
//roles membership
|
||||
@Override
|
||||
public Set<String> getRolesMembership() {
|
||||
return this.roleIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRolesMembership(Set<String> rolesMembership) {
|
||||
this.roleIds.clear();
|
||||
if (rolesMembership != null) this.roleIds.addAll(rolesMembership);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRolesMembership(String roleId) {
|
||||
this.roleIds.add(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRolesMembership(String roleId) {
|
||||
this.roleIds.remove(roleId);
|
||||
}
|
||||
|
||||
//user required actions
|
||||
@Override
|
||||
public Set<String> getRequiredActions() {
|
||||
return this.metadata.getRequiredActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(Set<String> requiredActions) {
|
||||
this.metadata.setRequiredActions(requiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String requiredAction) {
|
||||
this.metadata.addRequiredAction(requiredAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String requiredAction) {
|
||||
this.metadata.removeRequiredAction(requiredAction);
|
||||
}
|
||||
|
||||
//user attributes
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
for (JpaUserAttributeEntity attribute : this.attributes) {
|
||||
List<String> values = result.getOrDefault(attribute.getName(), new LinkedList<>());
|
||||
values.add(attribute.getValue());
|
||||
result.put(attribute.getName(), values);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
attributes.forEach(this::setAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return this.attributes.stream().filter(a -> Objects.equals(a.getName(), name))
|
||||
.map(JpaUserAttributeEntity::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
this.removeAttribute(name);
|
||||
if (values != null) {
|
||||
values.forEach(value -> this.attributes.add(new JpaUserAttributeEntity(this, name, value)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
this.attributes.removeIf(attr -> Objects.equals(attr.getName(), name));
|
||||
}
|
||||
|
||||
//user consents
|
||||
@Override
|
||||
public Set<MapUserConsentEntity> getUserConsents() {
|
||||
return this.consents.stream().map(MapUserConsentEntity.class::cast).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserConsents(Set<MapUserConsentEntity> userConsents) {
|
||||
this.consents.clear();
|
||||
if (userConsents != null) {
|
||||
userConsents.forEach(this::addUserConsent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUserConsent(MapUserConsentEntity userConsentEntity) {
|
||||
JpaUserConsentEntity entity = (JpaUserConsentEntity) CLONER.from(userConsentEntity);
|
||||
entity.setParent(this);
|
||||
entity.setEntityVersion(this.getEntityVersion());
|
||||
this.consents.add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeUserConsent(MapUserConsentEntity userConsentEntity) {
|
||||
return this.consents.removeIf(uc -> Objects.equals(uc.getClientId(), userConsentEntity.getClientId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeUserConsent(String clientId) {
|
||||
return this.consents.removeIf(uc -> Objects.equals(uc.getClientId(), clientId));
|
||||
}
|
||||
|
||||
//user credentials
|
||||
@Override
|
||||
public List<MapUserCredentialEntity> getCredentials() {
|
||||
return this.metadata.getCredentials();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCredentials(List<MapUserCredentialEntity> credentials) {
|
||||
this.metadata.setCredentials(credentials);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCredential(MapUserCredentialEntity credentialEntity) {
|
||||
this.metadata.addCredential(credentialEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeCredential(MapUserCredentialEntity credentialEntity) {
|
||||
return super.removeCredential(credentialEntity.getId());
|
||||
}
|
||||
|
||||
//user federated identities
|
||||
@Override
|
||||
public Set<MapUserFederatedIdentityEntity> getFederatedIdentities() {
|
||||
return this.federatedIdentities.stream().map(MapUserFederatedIdentityEntity.class::cast).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFederatedIdentities(Set<MapUserFederatedIdentityEntity> federatedIdentities) {
|
||||
this.federatedIdentities.clear();
|
||||
if (federatedIdentities != null) {
|
||||
federatedIdentities.forEach(this::addFederatedIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFederatedIdentity(MapUserFederatedIdentityEntity federatedIdentity) {
|
||||
JpaUserFederatedIdentityEntity entity = (JpaUserFederatedIdentityEntity) CLONER.from(federatedIdentity);
|
||||
entity.setParent(this);
|
||||
entity.setEntityVersion(this.getEntityVersion());
|
||||
this.federatedIdentities.add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeFederatedIdentity(MapUserFederatedIdentityEntity federatedIdentity) {
|
||||
return this.federatedIdentities.removeIf(fi -> Objects.equals(fi.getIdentityProvider(), federatedIdentity.getIdentityProvider()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeFederatedIdentity(String identityProviderId) {
|
||||
return this.federatedIdentities.removeIf(fi -> Objects.equals(fi.getIdentityProvider(), identityProviderId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaUserEntity)) return false;
|
||||
return Objects.equals(getId(), ((JpaUserEntity) obj).getId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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.user.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.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
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.UpdatableEntity;
|
||||
import org.keycloak.models.map.storage.jpa.JpaChildEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
||||
|
||||
/**
|
||||
* JPA {@link MapUserFederatedIdentityEntity} 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_federated_identity")
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaUserFederatedIdentityEntity extends UpdatableEntity.Impl implements MapUserFederatedIdentityEntity, JpaChildEntity<JpaUserEntity> {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@GeneratedValue
|
||||
private UUID id;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String identityProvider;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String userId;
|
||||
|
||||
@Type(type = "jsonb")
|
||||
@Column(columnDefinition = "jsonb")
|
||||
private final JpaUserFederatedIdentityMetadata metadata;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name="fk_root")
|
||||
private JpaUserEntity root;
|
||||
|
||||
public JpaUserFederatedIdentityEntity() {
|
||||
this.metadata = new JpaUserFederatedIdentityMetadata();
|
||||
}
|
||||
|
||||
public JpaUserFederatedIdentityEntity(final DeepCloner cloner) {
|
||||
this.metadata = new JpaUserFederatedIdentityMetadata(cloner);
|
||||
}
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return this.metadata.getEntityVersion();
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer version) {
|
||||
this.metadata.setEntityVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaUserEntity getParent() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
public void setParent(final JpaUserEntity root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return this.metadata.getToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToken(String token) {
|
||||
this.metadata.setToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserId() {
|
||||
return this.metadata.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserId(String userId) {
|
||||
this.metadata.setUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentityProvider() {
|
||||
return this.metadata.getIdentityProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityProvider(String identityProvider) {
|
||||
this.metadata.setIdentityProvider(identityProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return this.metadata.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
this.metadata.setUserName(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaUserFederatedIdentityEntity)) return false;
|
||||
return Objects.equals(id, ((JpaUserFederatedIdentityEntity) obj).id);
|
||||
}
|
||||
}
|
|
@ -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.user.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntityImpl;
|
||||
|
||||
/**
|
||||
* Class that contains all the user federated identity metadata that is written as JSON into the database.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserFederatedIdentityMetadata extends MapUserFederatedIdentityEntityImpl implements Serializable {
|
||||
|
||||
public JpaUserFederatedIdentityMetadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JpaUserFederatedIdentityMetadata(final DeepCloner cloner) {
|
||||
super(cloner);
|
||||
}
|
||||
|
||||
private Integer entityVersion;
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.entityVersion = entityVersion;
|
||||
}
|
||||
}
|
|
@ -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.user.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.user.MapUserEntityImpl;
|
||||
|
||||
/**
|
||||
* Class that contains all the user metadata that is written as JSON into the database.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaUserMetadata extends MapUserEntityImpl implements Serializable {
|
||||
|
||||
public JpaUserMetadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JpaUserMetadata(final DeepCloner cloner) {
|
||||
super(cloner);
|
||||
}
|
||||
|
||||
private Integer entityVersion;
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.entityVersion = entityVersion;
|
||||
}
|
||||
}
|
|
@ -29,4 +29,5 @@ limitations under the License.
|
|||
<include file="META-INF/jpa-roles-changelog.xml"/>
|
||||
<include file="META-INF/jpa-single-use-objects-changelog.xml"/>
|
||||
<include file="META-INF/jpa-user-login-failures-changelog.xml"/>
|
||||
<include file="META-INF/jpa-users-changelog.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,22 @@
|
|||
<?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">
|
||||
<include file="META-INF/users/jpa-users-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -38,5 +38,10 @@
|
|||
<class>org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectNoteEntity</class>
|
||||
<!--user-login-failures-->
|
||||
<class>org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity</class>
|
||||
<!--users-->
|
||||
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity</class>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
<?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">
|
||||
|
||||
<changeSet author="keycloak" id="users-1">
|
||||
|
||||
<createTable tableName="kc_user">
|
||||
<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">
|
||||
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
|
||||
<ext:column name="realmid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fRealmId"/>
|
||||
<ext:column name="username" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fUsername"/>
|
||||
<ext:column name="firstname" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fFirstName"/>
|
||||
<ext:column name="lastname" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fLastName"/>
|
||||
<ext:column name="email" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fEmail"/>
|
||||
<ext:column name="emailconstraint" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fEmailConstraint"/>
|
||||
<ext:column name="federationlink" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fFederationLink"/>
|
||||
<ext:column name="enabled" type="BOOLEAN" jsonColumn="metadata" jsonProperty="fEnabled"/>
|
||||
<ext:column name="emailverified" type="BOOLEAN" jsonColumn="metadata" jsonProperty="fEmailVerified"/>
|
||||
<ext:column name="timestamp" type="BIGINT" jsonColumn="metadata" jsonProperty="fCreatedTimestamp"/>
|
||||
</ext:addGeneratedColumn>
|
||||
<createIndex tableName="kc_user" indexName="user_entityVersion">
|
||||
<column name="entityversion"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user" indexName="user_realmId">
|
||||
<column name="realmid"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user" indexName="user_username_realmid">
|
||||
<column name="username"/>
|
||||
<column name="realmid"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user" indexName="user_email_realmid">
|
||||
<column name="email"/>
|
||||
<column name="realmid"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user" indexName="user_email_constraint_realmid">
|
||||
<column name="emailconstraint"/>
|
||||
<column name="realmid"/>
|
||||
</createIndex>
|
||||
<!-- TODO: create individual indexes for all other searchable columns (firstname, lastname, enabled, etc) ? -->
|
||||
<ext:createJsonIndex tableName="kc_user" indexName="user_serviceAccountClientLink">
|
||||
<ext:column jsonColumn="metadata" jsonProperty="fServiceAccountClientLink"/>
|
||||
</ext:createJsonIndex>
|
||||
|
||||
<createTable tableName="kc_user_consent">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="version" type="INTEGER" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="fk_root" type="UUID">
|
||||
<constraints foreignKeyName="user_consent_fk_root_fkey" references="kc_user(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="metadata" type="json"/>
|
||||
</createTable>
|
||||
<ext:addGeneratedColumn tableName="kc_user_consent">
|
||||
<ext:column name="clientid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fClientId"/>
|
||||
</ext:addGeneratedColumn>
|
||||
<createIndex tableName="kc_user_consent" indexName="user_consent_fk_root">
|
||||
<column name="fk_root"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_consent" indexName="user_consent_clientId">
|
||||
<column name="clientid"/>
|
||||
</createIndex>
|
||||
<ext:createJsonIndex tableName="kc_user_consent" indexName="user_consent_grantedClientScopes">
|
||||
<ext:column jsonColumn="metadata" jsonProperty="fGrantedClientScopesIds"/>
|
||||
</ext:createJsonIndex>
|
||||
|
||||
<createTable tableName="kc_user_federated_identity">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="version" type="INTEGER" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="fk_root" type="UUID">
|
||||
<constraints foreignKeyName="user_federated_identity_fk_root_fkey" references="kc_user(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="metadata" type="json"/>
|
||||
</createTable>
|
||||
<ext:addGeneratedColumn tableName="kc_user_federated_identity">
|
||||
<ext:column name="identityprovider" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fIdentityProvider"/>
|
||||
<ext:column name="userid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fUserId"/>
|
||||
</ext:addGeneratedColumn>
|
||||
<createIndex tableName="kc_user_federated_identity" indexName="user_federated_identity_fk_root">
|
||||
<column name="fk_root"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_federated_identity" indexName="user_federated_identity_idp">
|
||||
<column name="identityprovider"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_federated_identity" indexName="user_federated_identity_userId">
|
||||
<column name="userid"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="kc_user_group">
|
||||
<column name="user_id" type="UUID">
|
||||
<constraints nullable="false" foreignKeyName="user_group_userid_fkey" references="kc_user(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="group_id" type="KC_KEY"/>
|
||||
</createTable>
|
||||
<createIndex tableName="kc_user_group" indexName="user_group_userId">
|
||||
<column name="user_id"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_group" indexName="user_group_groupId">
|
||||
<column name="group_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="kc_user_role">
|
||||
<column name="user_id" type="UUID">
|
||||
<constraints nullable="false" foreignKeyName="user_role_userid_fkey" references="kc_user(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="role_id" type="KC_KEY"/>
|
||||
</createTable>
|
||||
<createIndex tableName="kc_user_role" indexName="user_role_userId">
|
||||
<column name="user_id"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_role" indexName="user_role_roleId">
|
||||
<column name="role_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="kc_user_attribute">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="fk_root" type="UUID">
|
||||
<constraints foreignKeyName="user_attr_fk_root_fkey" references="kc_user(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)"/>
|
||||
<column name="value" type="TEXT"/>
|
||||
</createTable>
|
||||
<createIndex tableName="kc_user_attribute" indexName="user_attr_fk_root">
|
||||
<column name="fk_root"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_user_attribute" indexName="user_attr_name_value">
|
||||
<column name="name"/>
|
||||
<column name="VALUE(255)" valueComputed="VALUE(255)"/>
|
||||
</createIndex>
|
||||
<modifySql dbms="postgresql,cockroachdb">
|
||||
<replace replace="VALUE(255)" with="(value::varchar(250))"/>
|
||||
</modifySql>
|
||||
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="users-2" dbms="postgresql">
|
||||
<!-- this is deferrable and initiallyDeferred as hibernate will first insert new entries and then delete the old by default -->
|
||||
<!-- this will not work on cockroachdb as deferred indexes are not supported in version 22.1 yet, therefore, only run it on postgresql -->
|
||||
<!-- see https://go.crdb.dev/issue-v/31632/v21.2 for the current status of the implementation -->
|
||||
<addUniqueConstraint tableName="kc_user_group" columnNames="user_id, group_id" deferrable="true" initiallyDeferred="true" />
|
||||
<addUniqueConstraint tableName="kc_user_role" columnNames="user_id, role_id" deferrable="true" initiallyDeferred="true" />
|
||||
<addUniqueConstraint tableName="kc_user_attribute" columnNames="fk_root, name, value" deferrable="true" initiallyDeferred="true" />
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -900,6 +900,7 @@
|
|||
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.map.storage.provider>
|
||||
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
|
||||
<keycloak.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
|
||||
<keycloak.user.map.storage.provider>jpa</keycloak.user.map.storage.provider>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.jboss.arquillian.core.api.Instance;
|
|||
import org.jboss.arquillian.core.api.InstanceProducer;
|
||||
import org.jboss.arquillian.core.api.annotation.Inject;
|
||||
import org.jboss.arquillian.test.spi.TestResult;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
|
@ -49,19 +50,23 @@ public class ModelTestExecutor extends LocalTestExecuter {
|
|||
super.execute(event);
|
||||
} else {
|
||||
TestResult result = new TestResult();
|
||||
if (annotation.skipForMapStorage() && Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) {
|
||||
result = TestResult.skipped();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
// Model test - wrap the call inside the
|
||||
TestContext ctx = testContext.get();
|
||||
KeycloakTestingClient testingClient = ctx.getTestingClient();
|
||||
testingClient.server().runModelTest(testMethod.getDeclaringClass().getName(), testMethod.getName());
|
||||
|
||||
try {
|
||||
// Model test - wrap the call inside the
|
||||
TestContext ctx = testContext.get();
|
||||
KeycloakTestingClient testingClient = ctx.getTestingClient();
|
||||
testingClient.server().runModelTest(testMethod.getDeclaringClass().getName(), testMethod.getName());
|
||||
|
||||
result.setStatus(TestResult.Status.PASSED);
|
||||
} catch (Throwable e) {
|
||||
result.setStatus(TestResult.Status.FAILED);
|
||||
result.setThrowable(e);
|
||||
} finally {
|
||||
result.setEnd(System.currentTimeMillis());
|
||||
result.setStatus(TestResult.Status.PASSED);
|
||||
} catch (Throwable e) {
|
||||
result.setStatus(TestResult.Status.FAILED);
|
||||
result.setThrowable(e);
|
||||
} finally {
|
||||
result.setEnd(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
// Need to use reflection this way...
|
||||
|
|
|
@ -33,4 +33,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Retention(RUNTIME)
|
||||
@Target({ElementType.METHOD}) // TODO: Maybe ElementClass.TYPE too? That way it will be possible to add the annotation on the the test class and not need to add on all the test methods inside the class
|
||||
public @interface ModelTest {
|
||||
|
||||
boolean skipForMapStorage() default false;
|
||||
}
|
||||
|
|
|
@ -33,12 +33,8 @@ public class BadRealmTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@ModelTest
|
||||
@ModelTest(skipForMapStorage = true) // when map storage is enabled, the id is always converted into a valid UUID.
|
||||
public void testBadRealmId(KeycloakSession session) {
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) {
|
||||
// when map storage is enabled, the id is always converted into a valid UUID.
|
||||
return;
|
||||
}
|
||||
RealmManager manager = new RealmManager(session);
|
||||
try {
|
||||
manager.createRealm(id + script, name);
|
||||
|
|
|
@ -196,7 +196,7 @@ public class ConcurrentTransactionsTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// KEYCLOAK-3296 , KEYCLOAK-3494
|
||||
@Test
|
||||
@ModelTest
|
||||
@ModelTest(skipForMapStorage = true) // skipped for map storage - to be revisited (GHI #12910)
|
||||
public void removeUserAttribute(KeycloakSession session) throws Exception {
|
||||
|
||||
try {
|
||||
|
|
|
@ -184,7 +184,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
user.singleAttribute("formatted", "6 Foo Street");
|
||||
user.singleAttribute("phone", "617-777-6666");
|
||||
user.singleAttribute("json-attribute", "{\"a\": 1, \"b\": 2, \"c\": [{\"a\": 1, \"b\": 2}], \"d\": {\"a\": 1, \"b\": 2}}");
|
||||
user.getAttributes().put("json-attribute-multi", Arrays.asList("{\"a\": 1, \"b\": 2, \"c\": [{\"a\": 1, \"b\": 2}], \"d\": {\"a\": 1, \"b\": 2}}", "{\"a\": 1, \"b\": 2, \"c\": [{\"a\": 1, \"b\": 2}], \"d\": {\"a\": 1, \"b\": 2}}"));
|
||||
user.getAttributes().put("json-attribute-multi", Arrays.asList("{\"a\": 1, \"b\": 2, \"c\": [{\"a\": 1, \"b\": 2}], \"d\": {\"a\": 1, \"b\": 2}}", "{\"a\": 3, \"b\": 4, \"c\": [{\"a\": 1, \"b\": 2}], \"d\": {\"a\": 1, \"b\": 2}}"));
|
||||
|
||||
List<String> departments = Arrays.asList("finance", "development");
|
||||
user.getAttributes().put("departments", departments);
|
||||
|
|
|
@ -88,16 +88,16 @@ public class JpaMapStorage extends KeycloakModelParameters {
|
|||
.config("driverDialect", "org.keycloak.models.map.storage.jpa.hibernate.dialect.JsonbPostgreSQL95Dialect");
|
||||
|
||||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
Loading…
Reference in a new issue