Decouple Identity provider mappers from RealmModel (#32251)

* Decouple Identity provider mappers from RealmModel

Closes #31731

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
Vlasta Ramik 2024-08-22 17:05:19 +02:00 committed by GitHub
parent 4450e0e455
commit d63c0fbd13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 434 additions and 177 deletions

View file

@ -1175,55 +1175,37 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public Stream<IdentityProviderMapperModel> getIdentityProviderMappersStream() {
if (isUpdated()) return updated.getIdentityProviderMappersStream();
return cached.getIdentityProviderMapperSet().stream();
return session.identityProviders().getMappersStream();
}
@Override
public Stream<IdentityProviderMapperModel> getIdentityProviderMappersByAliasStream(String brokerAlias) {
if (isUpdated()) return updated.getIdentityProviderMappersByAliasStream(brokerAlias);
Set<IdentityProviderMapperModel> mappings = new HashSet<>(cached.getIdentityProviderMappers().getList(brokerAlias));
return mappings.stream();
return session.identityProviders().getMappersByAliasStream(brokerAlias);
}
@Override
public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) {
getDelegateForUpdate();
return updated.addIdentityProviderMapper(model);
return session.identityProviders().createMapper(model);
}
@Override
public void removeIdentityProviderMapper(IdentityProviderMapperModel mapping) {
getDelegateForUpdate();
updated.removeIdentityProviderMapper(mapping);
session.identityProviders().removeMapper(mapping);
}
@Override
public void updateIdentityProviderMapper(IdentityProviderMapperModel mapping) {
getDelegateForUpdate();
updated.updateIdentityProviderMapper(mapping);
session.identityProviders().updateMapper(mapping);
}
@Override
public IdentityProviderMapperModel getIdentityProviderMapperById(String id) {
if (isUpdated()) return updated.getIdentityProviderMapperById(id);
for (List<IdentityProviderMapperModel> models : cached.getIdentityProviderMappers().values()) {
for (IdentityProviderMapperModel model : models) {
if (model.getId().equals(id)) return model;
}
}
return null;
return session.identityProviders().getMapperById(id);
}
@Override
public IdentityProviderMapperModel getIdentityProviderMapperByName(String alias, String name) {
if (isUpdated()) return updated.getIdentityProviderMapperByName(alias, name);
List<IdentityProviderMapperModel> models = cached.getIdentityProviderMappers().getList(alias);
if (models == null) return null;
for (IdentityProviderMapperModel model : models) {
if (model.getName().equals(name)) return model;
}
return null;
return session.identityProviders().getMapperByName(alias, name);
}
@Override

View file

@ -39,7 +39,6 @@ import org.keycloak.models.CibaConfig;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.OAuth2DeviceConfig;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.ParConfig;
@ -158,18 +157,12 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected String defaultRoleId;
private boolean allowUserManagedAccess;
public Set<IdentityProviderMapperModel> getIdentityProviderMapperSet() {
return identityProviderMapperSet;
}
protected List<String> defaultGroups;
protected List<String> defaultDefaultClientScopes = new LinkedList<>();
protected List<String> optionalDefaultClientScopes = new LinkedList<>();
protected boolean internationalizationEnabled;
protected Set<String> supportedLocales;
protected String defaultLocale;
protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
protected Set<IdentityProviderMapperModel> identityProviderMapperSet;
protected Map<String, String> attributes;
@ -245,13 +238,6 @@ public class CachedRealm extends AbstractExtendableRevisioned {
requiredCredentials = model.getRequiredCredentialsStream().collect(Collectors.toList());
userActionTokenLifespans = Collections.unmodifiableMap(new HashMap<>(model.getUserActionTokenLifespans()));
this.identityProviderMapperSet = model.getIdentityProviderMappersStream().collect(Collectors.toSet());
for (IdentityProviderMapperModel mapper : identityProviderMapperSet) {
identityProviderMappers.add(mapper.getIdentityProviderAlias(), mapper);
}
smtpConfig = model.getSmtpConfig();
browserSecurityHeaders = model.getBrowserSecurityHeaders();
@ -617,10 +603,6 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return defaultLocale;
}
public MultivaluedHashMap<String, IdentityProviderMapperModel> getIdentityProviderMappers() {
return identityProviderMappers;
}
public Map<String, AuthenticationFlowModel> getAuthenticationFlows() {
return authenticationFlows;
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2024 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.cache.infinispan.idp;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
import org.keycloak.models.cache.infinispan.entities.InRealm;
public class CachedIdentityProviderMapper extends AbstractRevisioned implements InRealm {
private final RealmModel realm;
private final IdentityProviderMapperModel mapper;
public CachedIdentityProviderMapper(Long revision, RealmModel realm, String cacheKey, IdentityProviderMapperModel mapper) {
super(revision, cacheKey);
this.realm = realm;
this.mapper = mapper;
}
@Override
public String getRealm() {
return realm.getId();
}
public IdentityProviderMapperModel getIdentityProviderMapper() {
return mapper;
}
}

View file

@ -20,8 +20,10 @@ import java.util.Map;
import java.util.stream.Stream;
import org.keycloak.common.Profile;
import org.keycloak.models.IDPProvider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.CacheRealmProvider;
@ -52,6 +54,10 @@ public class InfinispanIDPProvider implements IDPProvider {
return realm.getId() + "." + alias + IDP_ALIAS_KEY_SUFFIX;
}
private static String cacheKeyIdpMapperAliasName(RealmModel realm, String alias, String name) {
return realm.getId() + "." + alias + IDP_ALIAS_KEY_SUFFIX + "." + name;
}
@Override
public IdentityProviderModel create(IdentityProviderModel model) {
registerCountInvalidation();
@ -167,6 +173,81 @@ public class InfinispanIDPProvider implements IDPProvider {
idpDelegate.close();
}
@Override
public IdentityProviderMapperModel createMapper(IdentityProviderMapperModel model) {
return idpDelegate.createMapper(model);
}
@Override
public void updateMapper(IdentityProviderMapperModel model) {
registerIDPMapperInvalidation(model);
idpDelegate.updateMapper(model);
}
@Override
public boolean removeMapper(IdentityProviderMapperModel model) {
registerIDPMapperInvalidation(model);
return idpDelegate.removeMapper(model);
}
@Override
public void removeAllMappers() {
// no need to invalidate each entry in cache, removeAllMappers() is (currently) called only in case the realm is being deleted
idpDelegate.removeAllMappers();
}
@Override
public IdentityProviderMapperModel getMapperById(String id) {
CachedIdentityProviderMapper cached = realmCache.getCache().get(id, CachedIdentityProviderMapper.class);
String realmId = getRealm().getId();
if (cached != null && !cached.getRealm().equals(realmId)) {
cached = null;
}
if (cached == null) {
Long loaded = realmCache.getCache().getCurrentRevision(id);
IdentityProviderMapperModel model = idpDelegate.getMapperById(id);
if (model == null) return null;
if (isInvalid(id)) return model;
cached = new CachedIdentityProviderMapper(loaded, getRealm(), id, model);
realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision());
} else if (isInvalid(id)) {
return idpDelegate.getMapperById(id);
}
return cached.getIdentityProviderMapper();
}
@Override
public IdentityProviderMapperModel getMapperByName(String identityProviderAlias, String name) {
String cacheKey = cacheKeyIdpMapperAliasName(getRealm(), identityProviderAlias, name);
if (isInvalid(cacheKey)) {
return idpDelegate.getMapperByName(identityProviderAlias, name);
}
CachedIdentityProviderMapper cached = realmCache.getCache().get(cacheKey, CachedIdentityProviderMapper.class);
if (cached == null) {
Long loaded = realmCache.getCache().getCurrentRevision(cacheKey);
IdentityProviderMapperModel model = idpDelegate.getMapperByName(identityProviderAlias, name);
if (model == null) return null;
cached = new CachedIdentityProviderMapper(loaded, getRealm(), cacheKey, model);
realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision());
}
return cached.getIdentityProviderMapper();
}
@Override
public Stream<IdentityProviderMapperModel> getMappersStream() {
return idpDelegate.getMappersStream();
}
@Override
public Stream<IdentityProviderMapperModel> getMappersByAliasStream(String identityProviderAlias) {
return idpDelegate.getMappersByAliasStream(identityProviderAlias);
}
private void registerIDPInvalidation(IdentityProviderModel idp) {
realmCache.registerInvalidation(idp.getInternalId());
realmCache.registerInvalidation(cacheKeyIdpAlias(getRealm(), idp.getAlias()));
@ -176,6 +257,14 @@ public class InfinispanIDPProvider implements IDPProvider {
realmCache.registerInvalidation(cacheKeyIdpCount(getRealm()));
}
private void registerIDPMapperInvalidation(IdentityProviderMapperModel mapper) {
if (mapper.getId() == null) {
throw new ModelException("Identity Provider Mapper does not exist");
}
realmCache.registerInvalidation(mapper.getId());
realmCache.registerInvalidation(cacheKeyIdpMapperAliasName(getRealm(), mapper.getIdentityProviderAlias(), mapper.getName()));
}
private RealmModel getRealm() {
RealmModel realm = session.getContext().getRealm();
if (realm == null) {

View file

@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.util.stream.Collectors;
import org.hibernate.Session;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.IdentityProvider;
@ -38,11 +39,13 @@ import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.IDPProvider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.utils.StringUtil;
@ -163,6 +166,9 @@ public class JpaIDPProvider implements IDPProvider {
// flush so that constraint violations are flagged and converted into model exception now rather than at the end of the tx.
em.flush();
session.identityProviders().getMappersByAliasStream(alias).collect(Collectors.toList())
.forEach(session.identityProviders()::removeMapper);
// send identity provider removed event.
RealmModel realm = this.getRealm();
session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() {
@ -313,6 +319,104 @@ public class JpaIDPProvider implements IDPProvider {
public void close() {
}
@Override
public IdentityProviderMapperModel createMapper(IdentityProviderMapperModel model) {
checkUniqueMapperNamePerIdentityProvider(model);
IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity();
entity.setId(model.getId() == null ? KeycloakModelUtils.generateId() : model.getId());
entity.setName(model.getName());
entity.setIdentityProviderAlias(model.getIdentityProviderAlias());
entity.setIdentityProviderMapper(model.getIdentityProviderMapper());
entity.setRealmId(getRealm().getId());
entity.setConfig(model.getConfig());
em.persist(entity);
model.setId(entity.getId());
return model;
}
@Override
public void updateMapper(IdentityProviderMapperModel model) {
IdentityProviderMapperEntity entity = getMapperEntityById(model.getId(), true);
if (!model.getName().equals(entity.getName())) {
checkUniqueMapperNamePerIdentityProvider(model);
}
entity.setName(model.getName());
entity.setIdentityProviderAlias(model.getIdentityProviderAlias());
entity.setIdentityProviderMapper(model.getIdentityProviderMapper());
entity.setConfig(model.getConfig());
}
@Override
public boolean removeMapper(IdentityProviderMapperModel model) {
em.remove(getMapperEntityById(model.getId(), true));
return true;
}
@Override
public void removeAllMappers() {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaDelete<IdentityProviderMapperEntity> delete = builder.createCriteriaDelete(IdentityProviderMapperEntity.class);
Root<IdentityProviderMapperEntity> mapper = delete.from(IdentityProviderMapperEntity.class);
delete.where(builder.equal(mapper.get("realmId"), getRealm().getId()));
em.createQuery(delete).executeUpdate();
}
@Override
public IdentityProviderMapperModel getMapperById(String id) {
return toModel(getMapperEntityById(id, false));
}
@Override
public IdentityProviderMapperModel getMapperByName(String identityProviderAlias, String name) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<IdentityProviderMapperEntity> query = builder.createQuery(IdentityProviderMapperEntity.class);
Root<IdentityProviderMapperEntity> mapper = query.from(IdentityProviderMapperEntity.class);
Predicate predicate = builder.and(
builder.equal(mapper.get("realmId"), getRealm().getId()),
builder.equal(mapper.get("identityProviderAlias"), identityProviderAlias),
builder.equal(mapper.get("name"), name));
TypedQuery<IdentityProviderMapperEntity> typedQuery = em.createQuery(query.select(mapper).where(predicate));
try {
return toModel(typedQuery.getSingleResult());
} catch (NoResultException nre) {
return null;
}
}
@Override
public Stream<IdentityProviderMapperModel> getMappersStream() {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<IdentityProviderMapperEntity> query = builder.createQuery(IdentityProviderMapperEntity.class);
Root<IdentityProviderMapperEntity> mapper = query.from(IdentityProviderMapperEntity.class);
Predicate predicate = builder.equal(mapper.get("realmId"), getRealm().getId());
TypedQuery<IdentityProviderMapperEntity> typedQuery = em.createQuery(query.select(mapper).where(predicate));
return closing(typedQuery.getResultStream().map(this::toModel));
}
@Override
public Stream<IdentityProviderMapperModel> getMappersByAliasStream(String identityProviderAlias) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<IdentityProviderMapperEntity> query = builder.createQuery(IdentityProviderMapperEntity.class);
Root<IdentityProviderMapperEntity> mapper = query.from(IdentityProviderMapperEntity.class);
Predicate predicate = builder.and(
builder.equal(mapper.get("realmId"), getRealm().getId()),
builder.equal(mapper.get("identityProviderAlias"), identityProviderAlias));
TypedQuery<IdentityProviderMapperEntity> typedQuery = em.createQuery(query.select(mapper).where(predicate));
return closing(typedQuery.getResultStream().map(this::toModel));
}
private IdentityProviderEntity getEntityById(String id, boolean failIfNotFound) {
if (id == null) {
if (failIfNotFound) {
@ -407,6 +511,42 @@ public class JpaIDPProvider implements IDPProvider {
}
}
private void checkUniqueMapperNamePerIdentityProvider(IdentityProviderMapperModel model) {
if (session.identityProviders().getMapperByName(model.getIdentityProviderAlias(), model.getName()) != null) {
throw new ModelException("Identity provider mapper name must be unique per identity provider");
}
}
private IdentityProviderMapperEntity getMapperEntityById(String id, boolean failIfNotFound) {
IdentityProviderMapperEntity entity = em.find(IdentityProviderMapperEntity.class, id);
if (failIfNotFound && entity == null) {
throw new ModelException("Identity Provider Mapper with id [" + id + "] does not exist");
}
if (entity == null) return null;
// check realm to ensure this entity is fetched in the context of the correct realm.
if (!getRealm().getId().equals(entity.getRealmId())) {
throw new ModelException("Identity Provider Mapper with id [" + entity.getId() + "] does not belong to realm [" + getRealm().getName() + "]");
}
return entity;
}
private IdentityProviderMapperModel toModel(IdentityProviderMapperEntity entity) {
if (entity == null) return null;
IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
Map<String, String> config = entity.getConfig() == null ? new HashMap<>() : new HashMap<>(entity.getConfig());
mapping.setConfig(config);
return mapping;
}
private RealmModel getRealm() {
RealmModel realm = session.getContext().getRealm();
if (realm == null) {

View file

@ -205,6 +205,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
session.groups().preRemove(adapter);
session.identityProviders().removeAll();
session.identityProviders().removeAllMappers();
em.createNamedQuery("removeClientInitialAccessByRealm")
.setParameter("realm", realm).executeUpdate();

View file

@ -77,6 +77,7 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
this.realm = realm;
}
@Override
public RealmEntity getEntity() {
return realm;
}
@ -225,7 +226,7 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
@Override
public Map<String, String> getAttributes() {
// should always return a copy
Map<String, String> result = new HashMap<String, String>();
Map<String, String> result = new HashMap<>();
for (RealmAttributeEntity attr : realm.getAttributes()) {
result.put(attr.getName(), attr.getValue());
}
@ -1279,106 +1280,37 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
@Override
public Stream<IdentityProviderMapperModel> getIdentityProviderMappersStream() {
return realm.getIdentityProviderMappers().stream().map(this::entityToModel);
return session.identityProviders().getMappersStream();
}
@Override
public Stream<IdentityProviderMapperModel> getIdentityProviderMappersByAliasStream(String brokerAlias) {
return realm.getIdentityProviderMappers().stream()
.filter(e -> Objects.equals(e.getIdentityProviderAlias(), brokerAlias))
.map(this::entityToModel);
return session.identityProviders().getMappersByAliasStream(brokerAlias);
}
@Override
public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) {
if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getName()) != null) {
throw new RuntimeException("identity provider mapper name must be unique per identity provider");
}
String id = KeycloakModelUtils.generateId();
IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity();
entity.setId(id);
entity.setName(model.getName());
entity.setIdentityProviderAlias(model.getIdentityProviderAlias());
entity.setIdentityProviderMapper(model.getIdentityProviderMapper());
entity.setRealm(this.realm);
entity.setConfig(model.getConfig());
em.persist(entity);
this.realm.getIdentityProviderMappers().add(entity);
return entityToModel(entity);
}
protected IdentityProviderMapperEntity getIdentityProviderMapperEntity(String id) {
for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) {
if (entity.getId().equals(id)) {
return entity;
}
}
return null;
}
protected IdentityProviderMapperEntity getIdentityProviderMapperEntityByName(String alias, String name) {
for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) {
if (entity.getIdentityProviderAlias().equals(alias) && entity.getName().equals(name)) {
return entity;
}
}
return null;
return session.identityProviders().createMapper(model);
}
@Override
public void removeIdentityProviderMapper(IdentityProviderMapperModel mapping) {
IdentityProviderMapperEntity toDelete = getIdentityProviderMapperEntity(mapping.getId());
if (toDelete != null) {
this.realm.getIdentityProviderMappers().remove(toDelete);
em.remove(toDelete);
}
session.identityProviders().removeMapper(mapping);
}
@Override
public void updateIdentityProviderMapper(IdentityProviderMapperModel mapping) {
IdentityProviderMapperEntity entity = getIdentityProviderMapperEntity(mapping.getId());
entity.setIdentityProviderAlias(mapping.getIdentityProviderAlias());
entity.setIdentityProviderMapper(mapping.getIdentityProviderMapper());
if (entity.getConfig() == null) {
entity.setConfig(mapping.getConfig());
} else {
entity.getConfig().clear();
if (mapping.getConfig() != null) {
entity.getConfig().putAll(mapping.getConfig());
}
}
em.flush();
session.identityProviders().updateMapper(mapping);
}
@Override
public IdentityProviderMapperModel getIdentityProviderMapperById(String id) {
IdentityProviderMapperEntity entity = getIdentityProviderMapperEntity(id);
if (entity == null) return null;
return entityToModel(entity);
return session.identityProviders().getMapperById(id);
}
@Override
public IdentityProviderMapperModel getIdentityProviderMapperByName(String alias, String name) {
IdentityProviderMapperEntity entity = getIdentityProviderMapperEntityByName(alias, name);
if (entity == null) return null;
return entityToModel(entity);
}
protected IdentityProviderMapperModel entityToModel(IdentityProviderMapperEntity entity) {
IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
Map<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) config.putAll(entity.getConfig());
mapping.setConfig(config);
return mapping;
return session.identityProviders().getMapperByName(alias, name);
}
@Override

View file

@ -23,10 +23,8 @@ import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.Table;
import java.util.Map;
@ -58,9 +56,8 @@ public class IdentityProviderMapperEntity {
@CollectionTable(name="IDP_MAPPER_CONFIG", joinColumns={ @JoinColumn(name="IDP_MAPPER_ID") })
private Map<String, String> config;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "REALM_ID")
private RealmEntity realm;
@Column(name = "REALM_ID")
private String realmId;
public String getId() {
return id;
@ -94,12 +91,12 @@ public class IdentityProviderMapperEntity {
this.identityProviderMapper = identityProviderMapper;
}
public RealmEntity getRealm() {
return realm;
public String getRealmId() {
return realmId;
}
public void setRealm(RealmEntity realm) {
this.realm = realm;
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public Map<String, String> getConfig() {

View file

@ -187,9 +187,6 @@ public class RealmEntity {
@Column(name="DEFAULT_ROLE")
protected String defaultRoleId;
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<IdentityProviderMapperEntity> identityProviderMappers = new LinkedList<>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<AuthenticatorConfigEntity> authenticators = new LinkedList<>();
@ -640,17 +637,6 @@ public class RealmEntity {
this.defaultLocale = defaultLocale;
}
public Collection<IdentityProviderMapperEntity> getIdentityProviderMappers() {
if (identityProviderMappers == null) {
identityProviderMappers = new LinkedList<>();
}
return identityProviderMappers;
}
public void setIdentityProviderMappers(Collection<IdentityProviderMapperEntity> identityProviderMappers) {
this.identityProviderMappers = identityProviderMappers;
}
public Collection<AuthenticatorConfigEntity> getAuthenticatorConfigs() {
if (authenticators == null) {
authenticators = new LinkedList<>();

View file

@ -337,7 +337,7 @@ public class DefaultExportImportManager implements ExportImportManager {
}
importIdentityProviders(rep, newRealm, session);
importIdentityProviderMappers(rep, newRealm);
importIdentityProviderMappers(rep, session);
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
if (rep.getClientScopes() != null) {
@ -561,10 +561,10 @@ public class DefaultExportImportManager implements ExportImportManager {
}
}
private static void importIdentityProviderMappers(RealmRepresentation rep, RealmModel newRealm) {
private static void importIdentityProviderMappers(RealmRepresentation rep, KeycloakSession session) {
if (rep.getIdentityProviderMappers() != null) {
for (IdentityProviderMapperRepresentation representation : rep.getIdentityProviderMappers()) {
newRealm.addIdentityProviderMapper(RepresentationToModel.toModel(representation));
session.identityProviders().createMapper(RepresentationToModel.toModel(representation));
}
}
}

View file

@ -64,7 +64,7 @@ public abstract class AbstractConfigPropertySynchronizer<T extends ProviderEvent
"Reference of type '%s' changed from '%s' to '%s' in realm '%s'. Adjusting the reference from mapper '%s' of IDP '%s'.",
configPropertyName, configuredValue, newConfiguredValue, realm.getName(), idpMapper.getName(),
idpMapper.getIdentityProviderAlias());
realm.updateIdentityProviderMapper(idpMapper);
getKeycloakSession(event).identityProviders().updateMapper(idpMapper);
}
}

View file

@ -51,12 +51,7 @@ public final class ConfigSyncEventListener implements ProviderEventListener {
LOG.debugf("Synchronizer %s matches event: %s", configSynchronizer, event);
if (realmMappers == null) {
/*
* an event always refers to just one realm, so we can use an arbitrary synchronizer to extract the
* realm
*/
RealmModel realm = configSynchronizer.extractRealm(event);
realmMappers = realm.getIdentityProviderMappersStream()
realmMappers = configSynchronizer.getKeycloakSession(event).identityProviders().getMappersStream()
.collect(Collectors.toList());
}

View file

@ -18,6 +18,7 @@
package org.keycloak.broker.provider.mappersync;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
@ -31,5 +32,7 @@ public interface ConfigSynchronizer<T extends ProviderEvent> {
RealmModel extractRealm(T event);
KeycloakSession getKeycloakSession(T event);
void handleEvent(T event, IdentityProviderMapperModel idpMapper);
}

View file

@ -19,6 +19,7 @@ package org.keycloak.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -49,6 +50,11 @@ public class GroupConfigPropertyByPathSynchronizer extends AbstractConfigPropert
return event.getRealm();
}
@Override
public KeycloakSession getKeycloakSession(GroupModel.GroupPathChangeEvent event) {
return event.getKeycloakSession();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.GROUP;

View file

@ -19,6 +19,7 @@ package org.keycloak.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -49,6 +50,11 @@ public class RoleConfigPropertyByClientIdSynchronizer
return event.getUpdatedClient().getRealm();
}
@Override
public KeycloakSession getKeycloakSession(ClientModel.ClientIdChangeEvent event) {
return event.getKeycloakSession();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.ROLE;

View file

@ -18,6 +18,7 @@
package org.keycloak.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -49,6 +50,11 @@ public class RoleConfigPropertyByRoleNameSynchronizer
return event.getRealm();
}
@Override
public KeycloakSession getKeycloakSession(RoleModel.RoleNameChangeEvent event) {
return event.getKeycloakSession();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.ROLE;

View file

@ -517,7 +517,7 @@ public class ModelToRepresentation {
List<IdentityProviderRepresentation> identityProviders = session.identityProviders().getAllStream()
.map(provider -> toRepresentation(realm, provider, export)).collect(Collectors.toList());
rep.setIdentityProviders(identityProviders);
List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream()
List<IdentityProviderMapperRepresentation> identityProviderMappers = session.identityProviders().getMappersStream()
.map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
rep.setIdentityProviderMappers(identityProviderMappers);
}

View file

@ -245,4 +245,62 @@ public interface IDPProvider extends Provider {
.and(Stream.of(values()).map(LoginFilter::getFilter).reduce(Predicate::and).get());
}
}
/**
* Creates a new identity provider mapper from the specified model.
*
* @param model a {@link IdentityProviderMapperModel} containing the identity provider mapper's data.
* @return the model of the created identity provider mapper.
*/
IdentityProviderMapperModel createMapper(IdentityProviderMapperModel model);
/**
* Updates the identity provider mapper using the specified model.
*
* @param model a {@link IdentityProviderMapperModel} containing the identity provider mapper's data.
*/
void updateMapper(IdentityProviderMapperModel model);
/**
* Removes the given identity provider mapper.
*
* @param model a {@link IdentityProviderMapperModel} to be deleted.
* @return {@code true} if an identity provider mapper was removed; {@code false} otherwise.
*/
boolean removeMapper(IdentityProviderMapperModel model);
/**
* Removes all identity provider mappers from the realm.
*/
void removeAllMappers();
/**
* Obtains the identity provider mapper with the specified id.
*
* @param id the identity provider mapper's id.
* @return a reference to the identity provider mapper, or {@code null} if no mapper is found.
*/
IdentityProviderMapperModel getMapperById(String id);
/**
* Obtains the identity provider mapper with the provided identity provider alias and name.
*
* @param identityProviderAlias the identity provider alias.
* @param name the identity provider mapper's name.
* @return a reference to the identity provider mapper, or {@code null} if no provider is found.
*/
IdentityProviderMapperModel getMapperByName(String identityProviderAlias, String name);
/**
* Returns all identity provider mappers as a stream.
* @return Stream of {@link IdentityProviderMapperModel}. Never returns {@code null}.
*/
Stream<IdentityProviderMapperModel> getMappersStream();
/**
* Returns identity provider mappers by the provided alias as a stream.
* @param identityProviderAlias {@code String} Identity provider alias to filter results.
* @return Stream of {@link IdentityProviderMapperModel} Never returns {@code null}.
*/
Stream<IdentityProviderMapperModel> getMappersByAliasStream(String identityProviderAlias);
}

View file

@ -475,20 +475,48 @@ public interface RealmModel extends RoleContainerModel {
/**
* Returns identity provider mappers as a stream.
* @return Stream of {@link IdentityProviderMapperModel}. Never returns {@code null}.
* @deprecated Use {@link IDPProvider#getMappersStream()} instead.
*/
@Deprecated
Stream<IdentityProviderMapperModel> getIdentityProviderMappersStream();
/**
* Returns identity provider mappers by the provided alias as a stream.
* @param brokerAlias {@code String} Broker's alias to filter results.
* @return Stream of {@link IdentityProviderMapperModel} Never returns {@code null}.
* @deprecated Use {@link IDPProvider#getMappersByAliasStream(String)} instead.
*/
@Deprecated
Stream<IdentityProviderMapperModel> getIdentityProviderMappersByAliasStream(String brokerAlias);
/**
* @deprecated Use {@link IDPProvider#createMapper(IdentityProviderMapperModel)} instead.
*/
@Deprecated
IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model);
/**
* @deprecated Use {@link IDPProvider#removeMapper(IdentityProviderMapperModel)} instead.
*/
@Deprecated
void removeIdentityProviderMapper(IdentityProviderMapperModel mapping);
/**
* @deprecated Use {@link IDPProvider#updateMapper(IdentityProviderMapperModel)} instead.
*/
@Deprecated
void updateIdentityProviderMapper(IdentityProviderMapperModel mapping);
/**
* @deprecated Use {@link IDPProvider#getMapperById(String)} instead.
*/
@Deprecated
IdentityProviderMapperModel getIdentityProviderMapperById(String id);
/**
* @deprecated Use {@link IDPProvider#getMapperByName(String, String)} instead.
*/
@Deprecated
IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name);

View file

@ -436,11 +436,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
// Create the AttributeConsumingService if at least one attribute importer mapper exists
List<Entry<IdentityProviderMapperModel, SamlMetadataDescriptorUpdater>> metadataAttrProviders = new ArrayList<>();
realm.getIdentityProviderMappersByAliasStream(getConfig().getAlias())
session.identityProviders().getMappersByAliasStream(getConfig().getAlias())
.forEach(mapper -> {
IdentityProviderMapper target = (IdentityProviderMapper) session.getKeycloakSessionFactory().getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
if (target instanceof SamlMetadataDescriptorUpdater)
metadataAttrProviders.add(new java.util.AbstractMap.SimpleEntry<>(mapper, (SamlMetadataDescriptorUpdater)target));
if (target instanceof SamlMetadataDescriptorUpdater samlMetadataDescriptorUpdater)
metadataAttrProviders.add(new java.util.AbstractMap.SimpleEntry<>(mapper, samlMetadataDescriptorUpdater));
});
if (!metadataAttrProviders.isEmpty()) {

View file

@ -46,12 +46,12 @@ public class IdentityProviderMappersPartialImport extends AbstractPartialImport<
@Override
public String getModelId(RealmModel realm, KeycloakSession session, IdentityProviderMapperRepresentation idpMapperRep) {
return realm.getIdentityProviderMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName()).getId();
return session.identityProviders().getMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName()).getId();
}
@Override
public boolean exists(RealmModel realm, KeycloakSession session, IdentityProviderMapperRepresentation idpMapperRep) {
return realm.getIdentityProviderMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName()) != null;
return session.identityProviders().getMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName()) != null;
}
@Override
@ -66,19 +66,20 @@ public class IdentityProviderMappersPartialImport extends AbstractPartialImport<
@Override
public void remove(RealmModel realm, KeycloakSession session, IdentityProviderMapperRepresentation idpMapperRep) {
IdentityProviderMapperModel idpMapper = RepresentationToModel.toModel(idpMapperRep);
realm.removeIdentityProviderMapper(idpMapper);
IdentityProviderMapperModel idpMapper = session.identityProviders().getMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName());
if (idpMapper != null) {
session.identityProviders().removeMapper(idpMapper);
}
}
@Override
public void create(RealmModel realm, KeycloakSession session, IdentityProviderMapperRepresentation idpMapperRep) {
IdentityProviderMapperModel existing = realm.getIdentityProviderMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName());
if(existing != null) {
realm.removeIdentityProviderMapper(existing);
IdentityProviderMapperModel existing = session.identityProviders().getMapperByName(idpMapperRep.getIdentityProviderAlias(), idpMapperRep.getName());
if (existing != null) {
session.identityProviders().removeMapper(existing);
}
idpMapperRep.setId(KeycloakModelUtils.generateId());
IdentityProviderMapperModel idpMapper = RepresentationToModel.toModel(idpMapperRep);
realm.addIdentityProviderMapper(idpMapper);
session.identityProviders().createMapper(idpMapper);
}
}

View file

@ -574,7 +574,7 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
String providerId = identityProviderConfig.getAlias();
context.getIdp().preprocessFederatedIdentity(session, realm, context);
Set<IdentityProviderMapperModel> mappers = realm.getIdentityProviderMappersByAliasStream(context.getIdpConfig().getAlias())
Set<IdentityProviderMapperModel> mappers = session.identityProviders().getMappersByAliasStream(context.getIdpConfig().getAlias())
.collect(Collectors.toSet());
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (IdentityProviderMapperModel mapper : mappers) {

View file

@ -543,7 +543,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
context.getIdp().preprocessFederatedIdentity(session, realmModel, context);
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
realmModel.getIdentityProviderMappersByAliasStream(context.getIdpConfig().getAlias()).forEach(mapper -> {
session.identityProviders().getMappersByAliasStream(context.getIdpConfig().getAlias()).forEach(mapper -> {
IdentityProviderMapper target = (IdentityProviderMapper) sessionFactory
.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
target.preprocessFederatedIdentity(session, realmModel, mapper, context);
@ -729,7 +729,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
context.getIdp().importNewUser(session, realmModel, federatedUser, context);
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
realmModel.getIdentityProviderMappersByAliasStream(providerAlias).forEach(mapper -> {
session.identityProviders().getMappersByAliasStream(providerAlias).forEach(mapper -> {
IdentityProviderMapper target = (IdentityProviderMapper) sessionFactory
.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
target.importNewUser(session, realmModel, federatedUser, mapper, context);
@ -1052,7 +1052,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateToken(context, federatedUser, federatedIdentityModel);
context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
realmModel.getIdentityProviderMappersByAliasStream(context.getIdpConfig().getAlias()).forEach(mapper -> {
session.identityProviders().getMappersByAliasStream(context.getIdpConfig().getAlias()).forEach(mapper -> {
IdentityProviderMapper target = (IdentityProviderMapper) sessionFactory
.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
IdentityProviderMapperSyncModeDelegate.delegateUpdateBrokeredUser(session, realmModel, federatedUser, mapper, context, target);

View file

@ -344,7 +344,7 @@ public class IdentityProviderResource {
throw new jakarta.ws.rs.NotFoundException();
}
return realm.getIdentityProviderMappersByAliasStream(identityProviderModel.getAlias())
return session.identityProviders().getMappersByAliasStream(identityProviderModel.getAlias())
.map(ModelToRepresentation::toRepresentation);
}
@ -368,7 +368,8 @@ public class IdentityProviderResource {
IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
try {
model = realm.addIdentityProviderMapper(model);
// model = realm.addIdentityProviderMapper(model);
model = session.identityProviders().createMapper(model);
} catch (Exception e) {
throw ErrorResponse.error("Failed to add mapper '" + model.getName() + "' to identity provider [" + identityProviderModel.getProviderId() + "].", Response.Status.BAD_REQUEST);
}
@ -399,7 +400,7 @@ public class IdentityProviderResource {
throw new jakarta.ws.rs.NotFoundException();
}
IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
IdentityProviderMapperModel model = session.identityProviders().getMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
return ModelToRepresentation.toRepresentation(model);
}
@ -423,10 +424,10 @@ public class IdentityProviderResource {
throw new jakarta.ws.rs.NotFoundException();
}
IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
IdentityProviderMapperModel model = session.identityProviders().getMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
model = RepresentationToModel.toModel(rep);
realm.updateIdentityProviderMapper(model);
session.identityProviders().updateMapper(model);
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.IDENTITY_PROVIDER_MAPPER).resourcePath(session.getContext().getUri()).representation(rep).success();
}
@ -448,9 +449,9 @@ public class IdentityProviderResource {
throw new jakarta.ws.rs.NotFoundException();
}
IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
IdentityProviderMapperModel model = session.identityProviders().getMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
realm.removeIdentityProviderMapper(model);
session.identityProviders().removeMapper(model);
adminEvent.operation(OperationType.DELETE).resource(ResourceType.IDENTITY_PROVIDER_MAPPER).resourcePath(session.getContext().getUri()).success();
}