Convert MapClientScopeEntity to interface

Closes #9657
This commit is contained in:
vramik 2022-01-20 12:41:38 +01:00 committed by Hynek Mlnařík
parent e751626ac8
commit 873a44459a
7 changed files with 194 additions and 202 deletions

View file

@ -39,8 +39,11 @@ import java.util.stream.Stream;
*/
public abstract class MapClientAdapter extends AbstractClientModel<MapClientEntity> implements ClientModel {
private final MapProtocolMapperUtils pmUtils;
public MapClientAdapter(KeycloakSession session, RealmModel realm, MapClientEntity entity) {
super(session, realm, entity);
pmUtils = MapProtocolMapperUtils.instanceFor(safeGetProtocol());
}
@Override
@ -503,35 +506,15 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
/*************** Protocol mappers ****************/
private static MapProtocolMapperEntity fromModel(ProtocolMapperModel model) {
MapProtocolMapperEntity res = new MapProtocolMapperEntityImpl();
res.setId(model.getId());
res.setName(model.getName());
res.setProtocolMapper(model.getProtocolMapper());
res.setConfig(model.getConfig());
return res;
}
private ProtocolMapperModel toModel(MapProtocolMapperEntity entity) {
ProtocolMapperModel res = new ProtocolMapperModel();
res.setId(entity.getId());
res.setName(entity.getName());
res.setProtocolMapper(entity.getProtocolMapper());
res.setConfig(entity.getConfig());
res.setProtocol(safeGetProtocol());
return res;
private String safeGetProtocol() {
return entity.getProtocol() == null ? "openid-connect" : entity.getProtocol();
}
@Override
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
final Map<String, MapProtocolMapperEntity> protocolMappers = entity.getProtocolMappers();
return protocolMappers == null ? Stream.empty() : protocolMappers.values().stream().distinct()
.map(this::toModel);
}
private String safeGetProtocol() {
return entity.getProtocol() == null ? "openid-connect" : entity.getProtocol();
.map(pmUtils::toModel);
}
@Override
@ -540,7 +523,7 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
return null;
}
MapProtocolMapperEntity pm = fromModel(model);
MapProtocolMapperEntity pm = MapProtocolMapperUtils.fromModel(model);
if (pm.getId() == null) {
String id = KeycloakModelUtils.generateId();
pm.setId(id);
@ -550,7 +533,7 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
}
entity.setProtocolMapper(pm.getId(), pm);
return toModel(pm);
return pmUtils.toModel(pm);
}
@Override
@ -565,14 +548,14 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
public void updateProtocolMapper(ProtocolMapperModel mapping) {
final String id = mapping == null ? null : mapping.getId();
if (id != null) {
entity.setProtocolMapper(id, fromModel(mapping));
entity.setProtocolMapper(id, MapProtocolMapperUtils.fromModel(mapping));
}
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
MapProtocolMapperEntity protocolMapper = entity.getProtocolMapper(id);
return protocolMapper == null ? null : toModel(protocolMapper);
return protocolMapper == null ? null : pmUtils.toModel(protocolMapper);
}
@Override
@ -583,7 +566,7 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
}
return protocolMappers == null ? null : protocolMappers.values().stream()
.filter(pm -> Objects.equals(pm.getName(), name))
.map(this::toModel)
.map(pmUtils::toModel)
.findAny()
.orElse(null);
}

View file

@ -0,0 +1,59 @@
/*
* 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.client;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.keycloak.models.ProtocolMapperModel;
public class MapProtocolMapperUtils {
private final String protocol;
private static final ConcurrentMap<String, MapProtocolMapperUtils> INSTANCES = new ConcurrentHashMap<>();
private MapProtocolMapperUtils(String protocol) {
this.protocol = protocol;
}
public static MapProtocolMapperUtils instanceFor(String protocol) {
Objects.requireNonNull(protocol);
return INSTANCES.computeIfAbsent(protocol, MapProtocolMapperUtils::new);
}
public static MapProtocolMapperEntity fromModel(ProtocolMapperModel model) {
MapProtocolMapperEntity res = new MapProtocolMapperEntityImpl();
res.setId(model.getId());
res.setName(model.getName());
res.setProtocolMapper(model.getProtocolMapper());
res.setConfig(model.getConfig());
return res;
}
public ProtocolMapperModel toModel(MapProtocolMapperEntity entity) {
ProtocolMapperModel res = new ProtocolMapperModel();
res.setId(entity.getId());
res.setName(entity.getName());
res.setProtocolMapper(entity.getProtocolMapper());
res.setConfig(entity.getConfig());
res.setProtocol(protocol);
return res;
}
}

View file

@ -16,11 +16,13 @@
*/
package org.keycloak.models.map.clientscope;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.ClientScopeModel;
@ -28,13 +30,18 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RoleUtils;
public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientScopeEntity> implements ClientScopeModel {
private final MapProtocolMapperUtils pmUtils;
public MapClientScopeAdapter(KeycloakSession session, RealmModel realm, MapClientScopeEntity entity) {
super(session, realm, entity);
pmUtils = MapProtocolMapperUtils.instanceFor(safeGetProtocol());
}
@Override
@ -72,8 +79,21 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
entity.setProtocol(protocol);
}
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public void setAttribute(String name, String value) {
// TODO: https://github.com/keycloak/keycloak/issues/9741
// boolean valueUndefined = value == null || "".equals(value.trim());
//
// if (valueUndefined) {
// removeAttribute(name);
// return;
// }
entity.setAttribute(name, Collections.singletonList(value));
}
@ -85,13 +105,15 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
@Override
public String getAttribute(String name) {
List<String> attribute = entity.getAttribute(name);
if (attribute.isEmpty()) return null;
if (attribute == null || attribute.isEmpty()) return null;
return attribute.get(0);
}
@Override
public Map<String, String> getAttributes() {
return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
final Map<String, List<String>> attributes = entity.getAttributes();
final Map<String, List<String>> a = attributes == null ? Collections.emptyMap() : attributes;
return a.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
entry -> {
if (entry.getValue().isEmpty()) return null;
return entry.getValue().get(0);
@ -99,14 +121,16 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
);
}
@Override
public RealmModel getRealm() {
return realm;
/*************** Protocol mappers ****************/
private String safeGetProtocol() {
return entity.getProtocol() == null ? "openid-connect" : entity.getProtocol();
}
@Override
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return entity.getProtocolMappers().distinct();
final Set<MapProtocolMapperEntity> protocolMappers = entity.getProtocolMappers();
return protocolMappers == null ? Stream.empty() : protocolMappers.stream().distinct().map(pmUtils::toModel);
}
@Override
@ -115,19 +139,17 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
return null;
}
ProtocolMapperModel pm = new ProtocolMapperModel();
pm.setId(KeycloakModelUtils.generateId());
pm.setName(model.getName());
pm.setProtocol(model.getProtocol());
pm.setProtocolMapper(model.getProtocolMapper());
if (model.getConfig() != null) {
pm.setConfig(new HashMap<>(model.getConfig()));
} else {
MapProtocolMapperEntity pm = MapProtocolMapperUtils.fromModel(model);
if (pm.getId() == null) {
String id = KeycloakModelUtils.generateId();
pm.setId(id);
}
if (model.getConfig() == null) {
pm.setConfig(new HashMap<>());
}
return entity.addProtocolMapper(pm);
entity.addProtocolMapper(pm);
return pmUtils.toModel(pm);
}
@Override
@ -142,26 +164,37 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
public void updateProtocolMapper(ProtocolMapperModel mapping) {
final String id = mapping == null ? null : mapping.getId();
if (id != null) {
entity.updateProtocolMapper(id, mapping);
entity.getProtocolMapper(id).ifPresent((pmEntity) -> {
entity.removeProtocolMapper(id);
addProtocolMapper(mapping);
});
}
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return entity.getProtocolMapperById(id);
return entity.getProtocolMapper(id).map(pmUtils::toModel).orElse(null);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return entity.getProtocolMappers()
.filter(pm -> Objects.equals(pm.getProtocol(), protocol) && Objects.equals(pm.getName(), name))
.findAny()
.orElse(null);
final Set<MapProtocolMapperEntity> protocolMappers = entity.getProtocolMappers();
if (! Objects.equals(protocol, safeGetProtocol())) {
return null;
}
return protocolMappers == null ? null : protocolMappers.stream()
.filter(pm -> Objects.equals(pm.getName(), name))
.map(pmUtils::toModel)
.findAny()
.orElse(null);
}
/*************** Scopes mappings ****************/
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return this.entity.getScopeMappings()
final Collection<String> scopeMappings = this.entity.getScopeMappings();
return scopeMappings == null ? Stream.empty() : scopeMappings.stream()
.map(realm::getRoleById)
.filter(Objects::nonNull);
}
@ -183,7 +216,7 @@ public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientSco
public void deleteScopeMapping(RoleModel role) {
final String id = role == null ? null : role.getId();
if (id != null) {
this.entity.deleteScopeMapping(id);
this.entity.removeScopeMapping(id);
}
}

View file

@ -17,162 +17,75 @@
package org.keycloak.models.map.clientscope;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.EntityWithAttributes;
import org.keycloak.models.map.common.UpdatableEntity;
public class MapClientScopeEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes {
@GenerateEntityImplementations(
inherits = "org.keycloak.models.map.clientscope.MapClientScopeEntity.AbstractClientScopeEntity"
)
@DeepCloner.Root
public interface MapClientScopeEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes {
private String id;
private String realmId;
public abstract class AbstractClientScopeEntity extends UpdatableEntity.Impl implements MapClientScopeEntity {
private String name;
private String protocol;
private String description;
private String id;
private final Set<String> scopeMappings = new LinkedHashSet<>();
private final Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private final Map<String, List<String>> attributes = new HashMap<>();
@Override
public String getId() {
return this.id;
}
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
@Override
public void setId(String id) {
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
this.id = id;
this.updated |= id != null;
}
public MapClientScopeEntity() {}
@Override
public Optional<MapProtocolMapperEntity> getProtocolMapper(String id) {
Set<MapProtocolMapperEntity> mappers = getProtocolMappers();
if (mappers == null || mappers.isEmpty()) return Optional.empty();
public MapClientScopeEntity(String id, String realmId) {
this.id = id;
this.realmId = realmId;
}
return mappers.stream().filter(m -> Objects.equals(m.getId(), id)).findFirst();
}
@Override
public String getId() {
return this.id;
}
@Override
public void setId(String id) {
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
this.id = id;
this.updated |= id != null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
@Override
public Map<String, List<String>> getAttributes() {
return attributes;
}
@Override
public void setAttributes(Map<String, List<String>> attributes) {
this.attributes.clear();
this.attributes.putAll(attributes);
this.updated = true;
}
@Override
public void setAttribute(String name, List<String> values) {
this.updated |= ! Objects.equals(this.attributes.put(name, values), values);
}
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
public Stream<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values().stream();
}
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
@Override
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
@Override
public List<String> getAttribute(String name) {
return attributes.getOrDefault(name, Collections.EMPTY_LIST);
}
public String getRealmId() {
return this.realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public Stream<String> getScopeMappings() {
return scopeMappings.stream();
}
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
@Override
public void removeProtocolMapper(String id) {
Set<MapProtocolMapperEntity> mappers = getProtocolMappers();
this.updated |= mappers != null && mappers.removeIf(m -> Objects.equals(m.getId(), id));
}
}
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
String getName();
String getDescription();
String getProtocol();
String getRealmId();
void setName(String name);
void setDescription(String description);
void setProtocol(String protocol);
void setRealmId(String realmId);
Optional<MapProtocolMapperEntity> getProtocolMapper(String id);
Set<MapProtocolMapperEntity> getProtocolMappers();
void addProtocolMapper(MapProtocolMapperEntity mapping);
void removeProtocolMapper(String id);
void addScopeMapping(String id);
void removeScopeMapping(String id);
Collection<String> getScopeMappings();
}

View file

@ -77,7 +77,6 @@ public class MapClientScopeProvider implements ClientScopeProvider {
@Override
public ClientScopeModel addClientScope(RealmModel realm, String id, String name) {
// Check Db constraint: @UniqueConstraint(columnNames = {"REALM_ID", "NAME"})
DefaultModelCriteria<ClientScopeModel> mcb = criteria();
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.NAME, Operator.EQ, name);
@ -86,13 +85,17 @@ public class MapClientScopeProvider implements ClientScopeProvider {
throw new ModelDuplicateException("Client scope with name '" + name + "' in realm " + realm.getName());
}
LOG.tracef("addClientScope(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
MapClientScopeEntity entity = new MapClientScopeEntity(id, realm.getId());
entity.setName(KeycloakModelUtils.convertClientScopeName(name));
if (id != null && tx.read(id) != null) {
throw new ModelDuplicateException("Client scope exists: " + id);
}
LOG.tracef("addClientScope(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
MapClientScopeEntity entity = new MapClientScopeEntityImpl();
entity.setId(id);
entity.setRealmId(realm.getId());
entity.setName(KeycloakModelUtils.convertClientScopeName(name));
entity = tx.create(entity);
return entityToAdapterFunc(realm).apply(entity);
}

View file

@ -28,6 +28,7 @@ import org.keycloak.models.UserSessionModel;
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.MapClientScopeEntityImpl;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.Serialization;
@ -44,12 +45,9 @@ 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.user.MapUserConsentEntity;
import org.keycloak.models.map.user.MapUserConsentEntityImpl;
import org.keycloak.models.map.user.MapUserCredentialEntity;
import org.keycloak.models.map.user.MapUserCredentialEntityImpl;
import org.keycloak.models.map.user.MapUserEntityImpl;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntityImpl;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
@ -95,6 +93,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
.constructor(MapUserCredentialEntityImpl.class, MapUserCredentialEntityImpl::new)
.constructor(MapUserFederatedIdentityEntityImpl.class, MapUserFederatedIdentityEntityImpl::new)
.constructor(MapUserConsentEntityImpl.class, MapUserConsentEntityImpl::new)
.constructor(MapClientScopeEntityImpl.class, MapClientScopeEntityImpl::new)
.build();
private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>();

View file

@ -17,6 +17,7 @@
package org.keycloak.migration.migrators;
import java.util.stream.Collectors;
import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
@ -70,6 +71,7 @@ public class MigrationUtils {
client.getProtocolMappersStream()
.filter(mapper -> !mapper.getConfig().containsKey("userinfo.token.claim") && mapper.getConfig().containsKey("id.token.claim"))
.peek(mapper -> mapper.getConfig().put("userinfo.token.claim", mapper.getConfig().get("id.token.claim")))
.collect(Collectors.toSet()).stream() // to avoid ConcurrentModificationException
.forEach(client::updateProtocolMapper);
}