diff --git a/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java b/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java index ba96d7be0b..3261b76234 100644 --- a/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java +++ b/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java @@ -295,6 +295,24 @@ public class DeepCloner { return res; } + /** + * Returns a class type of an instance that would be instantiated by {@link #newInstance(java.lang.Class)} method. + * @param Type (class or a {@code @Root} interface) to create a new instance + * @param clazz Type (class or a {@code @Root} interface) to create a new instance + * @return See description + */ + public Class newInstanceType(Class valueType) { + if (valueType == null) { + return null; + } + try { + V v = newInstance(valueType); + return v == null ? null : (Class) v.getClass(); + } catch (IllegalStateException ex) { + return null; + } + } + /** * Deeply clones properties from the {@code from} instance to the {@code to} instance. * @param Type (class or a {@code @Root} interface) to clone the instance diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java b/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java new file mode 100644 index 0000000000..5705c80bde --- /dev/null +++ b/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java @@ -0,0 +1,144 @@ +/* + * Copyright 2021 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; + +import org.keycloak.authorization.model.PermissionTicket; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.AuthenticatedClientSessionModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserLoginFailureModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; +import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; +import org.keycloak.models.map.authorization.entity.MapPolicyEntity; +import org.keycloak.models.map.authorization.entity.MapResourceEntity; +import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; +import org.keycloak.models.map.authorization.entity.MapScopeEntity; +import org.keycloak.models.map.client.MapClientEntity; +import org.keycloak.models.map.clientscope.MapClientScopeEntity; +import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.group.MapGroupEntity; +import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; +import org.keycloak.models.map.realm.MapRealmEntity; +import org.keycloak.models.map.role.MapRoleEntity; +import org.keycloak.models.map.user.MapUserEntity; +import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; +import org.keycloak.models.map.userSession.MapUserSessionEntity; +import org.keycloak.sessions.RootAuthenticationSessionModel; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Utility class covering various aspects of relationship between model and entity classes. + * @author hmlnarik + */ +public class ModelEntityUtil { + + private static final Map, String> MODEL_TO_NAME = new HashMap<>(); + static { + MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions"); + MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes"); + MODEL_TO_NAME.put(ClientModel.class, "clients"); + MODEL_TO_NAME.put(GroupModel.class, "groups"); + MODEL_TO_NAME.put(RealmModel.class, "realms"); + MODEL_TO_NAME.put(RoleModel.class, "roles"); + MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions"); + MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures"); + MODEL_TO_NAME.put(UserModel.class, "users"); + MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions"); + + // authz + MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets"); + MODEL_TO_NAME.put(Policy.class, "authz-policies"); + MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers"); + MODEL_TO_NAME.put(Resource.class, "authz-resources"); + MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes"); + } + private static final Map> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey)); + + private static final Map, Class> MODEL_TO_ENTITY_TYPE = new HashMap<>(); + static { + MODEL_TO_ENTITY_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class); + MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class); + MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class); + MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class); + MODEL_TO_ENTITY_TYPE.put(RealmModel.class, MapRealmEntity.class); + MODEL_TO_ENTITY_TYPE.put(RoleModel.class, MapRoleEntity.class); + MODEL_TO_ENTITY_TYPE.put(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionEntity.class); + MODEL_TO_ENTITY_TYPE.put(UserLoginFailureModel.class, MapUserLoginFailureEntity.class); + MODEL_TO_ENTITY_TYPE.put(UserModel.class, MapUserEntity.class); + MODEL_TO_ENTITY_TYPE.put(UserSessionModel.class, MapUserSessionEntity.class); + + // authz + MODEL_TO_ENTITY_TYPE.put(PermissionTicket.class, MapPermissionTicketEntity.class); + MODEL_TO_ENTITY_TYPE.put(Policy.class, MapPolicyEntity.class); + MODEL_TO_ENTITY_TYPE.put(ResourceServer.class, MapResourceServerEntity.class); + MODEL_TO_ENTITY_TYPE.put(Resource.class, MapResourceEntity.class); + MODEL_TO_ENTITY_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class); + } + private static final Map, Class> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey)); + + @SuppressWarnings("unchecked") + public static Class getEntityType(Class modelClass) { + return (Class) MODEL_TO_ENTITY_TYPE.get(modelClass); + } + + @SuppressWarnings("unchecked") + public static Class getEntityType(Class modelClass, Class defaultClass) { + return (Class) MODEL_TO_ENTITY_TYPE.getOrDefault(modelClass, defaultClass); + } + + @SuppressWarnings("unchecked") + public static Class getModelType(Class entityClass) { + return (Class) ENTITY_TO_MODEL_TYPE.get(entityClass); + } + + @SuppressWarnings("unchecked") + public static Class getModelType(Class entityClass, Class defaultClass) { + return (Class) ENTITY_TO_MODEL_TYPE.getOrDefault(entityClass, defaultClass); + } + + public static String getModelName(Class key, String defaultValue) { + return MODEL_TO_NAME.getOrDefault(key, defaultValue); + } + + public static String getModelName(Class key) { + return MODEL_TO_NAME.get(key); + } + + public static Set getModelNames() { + return NAME_TO_MODEL.keySet(); + } + + @SuppressWarnings("unchecked") + public static Class getModelClass(String key) { + return (Class) NAME_TO_MODEL.get(key); + } + + + +} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java index c56c179f9a..bef9d75e15 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java @@ -19,43 +19,20 @@ package org.keycloak.models.map.storage.chm; import org.keycloak.models.map.common.StringKeyConvertor; import org.keycloak.component.AmphibianProviderFactory; import org.keycloak.Config.Scope; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; import org.keycloak.common.Profile; import org.keycloak.component.ComponentModelScope; import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; 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.UserSessionModel; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.client.MapClientEntity; import org.keycloak.models.map.client.MapClientEntityImpl; import org.keycloak.models.map.client.MapProtocolMapperEntity; import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.DeepCloner; import org.keycloak.models.map.common.Serialization; import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.group.MapGroupEntity; import org.keycloak.models.map.group.MapGroupEntityImpl; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.role.MapRoleEntity; import org.keycloak.models.map.role.MapRoleEntityImpl; import com.fasterxml.jackson.databind.JavaType; import java.io.File; @@ -67,18 +44,17 @@ import java.util.concurrent.ConcurrentHashMap; import org.jboss.logging.Logger; import org.keycloak.models.map.storage.MapStorageProvider; import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; +import org.keycloak.models.map.storage.ModelEntityUtil; import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.sessions.RootAuthenticationSessionModel; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName; +import static org.keycloak.models.map.storage.ModelEntityUtil.getModelNames; import static org.keycloak.models.map.storage.QueryParameters.withCriteria; import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; @@ -110,68 +86,6 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide .constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new) .build(); - public static final Map, String> MODEL_TO_NAME = new HashMap<>(); - static { - MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions"); - MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes"); - MODEL_TO_NAME.put(ClientModel.class, "clients"); - MODEL_TO_NAME.put(GroupModel.class, "groups"); - MODEL_TO_NAME.put(RealmModel.class, "realms"); - MODEL_TO_NAME.put(RoleModel.class, "roles"); - MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions"); - MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures"); - MODEL_TO_NAME.put(UserModel.class, "users"); - MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions"); - - // authz - MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets"); - MODEL_TO_NAME.put(Policy.class, "authz-policies"); - MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers"); - MODEL_TO_NAME.put(Resource.class, "authz-resources"); - MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes"); - } - - public static final Map, Class> MODEL_TO_VALUE_TYPE = new HashMap<>(); - static { - MODEL_TO_VALUE_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class); - MODEL_TO_VALUE_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class); - MODEL_TO_VALUE_TYPE.put(ClientModel.class, MapClientEntity.class); - MODEL_TO_VALUE_TYPE.put(GroupModel.class, MapGroupEntity.class); - MODEL_TO_VALUE_TYPE.put(RealmModel.class, MapRealmEntity.class); - MODEL_TO_VALUE_TYPE.put(RoleModel.class, MapRoleEntity.class); - MODEL_TO_VALUE_TYPE.put(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionEntity.class); - MODEL_TO_VALUE_TYPE.put(UserLoginFailureModel.class, MapUserLoginFailureEntity.class); - MODEL_TO_VALUE_TYPE.put(UserModel.class, MapUserEntity.class); - MODEL_TO_VALUE_TYPE.put(UserSessionModel.class, MapUserSessionEntity.class); - - // authz - MODEL_TO_VALUE_TYPE.put(PermissionTicket.class, MapPermissionTicketEntity.class); - MODEL_TO_VALUE_TYPE.put(Policy.class, MapPolicyEntity.class); - MODEL_TO_VALUE_TYPE.put(ResourceServer.class, MapResourceServerEntity.class); - MODEL_TO_VALUE_TYPE.put(Resource.class, MapResourceEntity.class); - MODEL_TO_VALUE_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class); - } - - public static final Map, Class> INTERFACE_TO_IMPL = new HashMap<>(); - static { - INTERFACE_TO_IMPL.put(MapClientEntity.class, MapClientEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapClientScopeEntity.class, MapClientScopeEntityImpl.class); - INTERFACE_TO_IMPL.put(MapGroupEntity.class, MapGroupEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapRealmEntity.class, MapRealmEntityImpl.class); - INTERFACE_TO_IMPL.put(MapRoleEntity.class, MapRoleEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapRootAuthenticationSessionEntity.class, MapRootAuthenticationSessionEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapUserLoginFailureEntity.class, MapUserLoginFailureEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapUserEntity.class, MapUserEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapUserSessionEntity.class, MapUserSessionEntityImpl.class); -// -// // authz -// INTERFACE_TO_IMPL.put(MapPermissionTicketEntity.class, MapPermissionTicketEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapPolicyEntity.class, MapPolicyEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapResourceServerEntity.class, MapResourceServerEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapResourceEntity.class, MapResourceEntityImpl.class); -// INTERFACE_TO_IMPL.put(MapScopeEntity.class, MapScopeEntityImpl.class); - } - private static final Map KEY_CONVERTORS = new HashMap<>(); static { KEY_CONVERTORS.put("uuid", StringKeyConvertor.UUIDKey.INSTANCE); @@ -195,7 +109,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide final String keyType = config.get("keyType", "uuid"); defaultKeyConvertor = getKeyConvertor(keyType); - for (String name : MODEL_TO_NAME.values()) { + for (String name : getModelNames()) { keyConvertors.put(name, getKeyConvertor(config.get("keyType." + name, keyType))); } @@ -259,7 +173,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide private ConcurrentHashMapStorage loadMap(String mapName, Class modelType, EnumSet flags) { final StringKeyConvertor kc = keyConvertors.getOrDefault(mapName, defaultKeyConvertor); - Class valueType = MODEL_TO_VALUE_TYPE.get(modelType); + Class valueType = ModelEntityUtil.getEntityType(modelType); LOG.debugf("Initializing new map storage: %s", mapName); ConcurrentHashMapStorage store; @@ -285,7 +199,10 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide if (f != null && f.exists()) { try { LOG.debugf("Restoring contents from %s", f.getCanonicalPath()); - Class valueImplType = INTERFACE_TO_IMPL.getOrDefault(valueType, valueType); + Class valueImplType = CLONER.newInstanceType(valueType); + if (valueImplType == null) { + valueImplType = valueType; + } JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(LinkedList.class, valueImplType); List values = Serialization.MAPPER.readValue(f, type); @@ -308,7 +225,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide public ConcurrentHashMapStorage getStorage( Class modelType, Flag... flags) { EnumSet f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags); - String name = MODEL_TO_NAME.getOrDefault(modelType, modelType.getSimpleName()); + String name = getModelName(modelType, modelType.getSimpleName()); /* From ConcurrentHashMapStorage.computeIfAbsent javadoc: * * "... the computation [...] must not attempt to update any other mappings of this map."