Provide a cache layer for the organization model
Closes #30087 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
parent
08ead04c43
commit
d355e38424
11 changed files with 593 additions and 8 deletions
|
@ -172,6 +172,29 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||||
return groupDelegate;
|
return groupDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getInvalidations() {
|
||||||
|
return Collections.unmodifiableSet(invalidations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmCacheManager getCache() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartupRevision() {
|
||||||
|
return startupRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerInvalidation(String id) {
|
||||||
|
invalidations.add(id);
|
||||||
|
invalidationEvents.add(new InvalidationEvent() {
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerRealmInvalidation(String id, String name) {
|
public void registerRealmInvalidation(String id, String name) {
|
||||||
cache.realmUpdated(id, name, invalidations);
|
cache.realmUpdated(id, name, invalidations);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* 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.organization;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.OrganizationDomainModel;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
|
||||||
|
import org.keycloak.models.cache.infinispan.LazyLoader;
|
||||||
|
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||||
|
import org.keycloak.models.cache.infinispan.entities.InRealm;
|
||||||
|
|
||||||
|
public class CachedOrganization extends AbstractRevisioned implements InRealm {
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final String name;
|
||||||
|
private final String description;
|
||||||
|
private final boolean enabled;
|
||||||
|
private final LazyLoader<OrganizationModel, MultivaluedHashMap<String, String>> attributes;
|
||||||
|
private final Set<OrganizationDomainModel> domains;
|
||||||
|
private final Set<IdentityProviderModel> idps;
|
||||||
|
|
||||||
|
public CachedOrganization(Long revision, RealmModel realm, OrganizationModel organization) {
|
||||||
|
super(revision, organization.getId());
|
||||||
|
this.realm = realm;
|
||||||
|
this.name = organization.getName();
|
||||||
|
this.description = organization.getDescription();
|
||||||
|
this.enabled = organization.isEnabled();
|
||||||
|
this.attributes = new DefaultLazyLoader<>(orgModel -> new MultivaluedHashMap<>(orgModel.getAttributes()), MultivaluedHashMap::new);
|
||||||
|
this.domains = organization.getDomains().collect(Collectors.toSet());
|
||||||
|
this.idps = organization.getIdentityProviders().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRealm() {
|
||||||
|
return realm.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmModel getRealmModel() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultivaluedHashMap<String, String> getAttributes(Supplier<OrganizationModel> organizationModel) {
|
||||||
|
return attributes.get(organizationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<OrganizationDomainModel> getDomains() {
|
||||||
|
return domains.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<IdentityProviderModel> getIdentityProviders() {
|
||||||
|
return idps.stream();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* 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.organization;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
|
import org.keycloak.models.cache.infinispan.RealmCacheSession;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
|
||||||
|
public class InfinispanOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final OrganizationProvider jpaOrgDelegate;
|
||||||
|
private final RealmCacheSession realmCache;
|
||||||
|
private final Map<String, OrganizationAdapter> managedOrganizations = new HashMap<>();
|
||||||
|
|
||||||
|
public InfinispanOrganizationProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
this.jpaOrgDelegate = session.getProvider(OrganizationProvider.class, "jpa");
|
||||||
|
this.realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrganizationModel create(String name) {
|
||||||
|
return jpaOrgDelegate.create(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrganizationModel getById(String id) {
|
||||||
|
CachedOrganization cached = realmCache.getCache().get(id, CachedOrganization.class);
|
||||||
|
String realmId = getRealm().getId();
|
||||||
|
if (cached != null && !cached.getRealm().equals(realmId)) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = realmCache.getCache().getCurrentRevision(id);
|
||||||
|
OrganizationModel model = jpaOrgDelegate.getById(id);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (realmCache.getInvalidations().contains(id)) return model;
|
||||||
|
cached = new CachedOrganization(loaded, getRealm(), model);
|
||||||
|
realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision());
|
||||||
|
|
||||||
|
// no need to check for realm invalidation as IdP changes are handled by events within InfinispanOrganizationProviderFactory
|
||||||
|
} else if (realmCache.getInvalidations().contains(id)) {
|
||||||
|
return orgDelegate.getById(id);
|
||||||
|
} else if (managedOrganizations.containsKey(id)) {
|
||||||
|
return managedOrganizations.get(id);
|
||||||
|
}
|
||||||
|
OrganizationAdapter adapter = new OrganizationAdapter(cached, realmCache, jpaOrgDelegate);
|
||||||
|
managedOrganizations.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrganizationModel getByDomainName(String domainName) {
|
||||||
|
String cacheKey = getRealm().getId() + "+.org.domain.name." + domainName;
|
||||||
|
CachedOrganization cached = realmCache.getCache().get(cacheKey, CachedOrganization.class);
|
||||||
|
String realmId = getRealm().getId();
|
||||||
|
if (cached != null && !cached.getRealm().equals(realmId)) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = realmCache.getCache().getCurrentRevision(cacheKey);
|
||||||
|
OrganizationModel model = jpaOrgDelegate.getByDomainName(domainName);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (realmCache.getInvalidations().contains(model.getId())) return model;
|
||||||
|
cached = new CachedOrganization(loaded, getRealm(), model);
|
||||||
|
realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision());
|
||||||
|
|
||||||
|
// no need to check for realm invalidation as IdP changes are handled by events within InfinispanOrganizationProviderFactory
|
||||||
|
} else if (realmCache.getInvalidations().contains(cached.getId())) {
|
||||||
|
return orgDelegate.getByDomainName(domainName);
|
||||||
|
} else if (managedOrganizations.containsKey(cached.getId())) {
|
||||||
|
return managedOrganizations.get(cached.getId());
|
||||||
|
}
|
||||||
|
OrganizationAdapter adapter = new OrganizationAdapter(cached, realmCache, jpaOrgDelegate);
|
||||||
|
managedOrganizations.put(cacheKey, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<OrganizationModel> getAllStream(String search, Boolean exact, Integer first, Integer max) {
|
||||||
|
// Return cache delegates to ensure cache invalidation during write operations
|
||||||
|
return getCacheDelegates(jpaOrgDelegate.getAllStream(search, exact, first, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<OrganizationModel> getAllStream(Map<String, String> attributes, Integer first, Integer max) {
|
||||||
|
// Return cache delegates to ensure cache invalidation during write operations
|
||||||
|
return getCacheDelegates(jpaOrgDelegate.getAllStream(attributes, first, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(OrganizationModel organization) {
|
||||||
|
registerOrganizationInvalidation(organization.getId());
|
||||||
|
return jpaOrgDelegate.remove(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAll() {
|
||||||
|
//TODO: won't scale, requires a better mechanism for bulk deleting organizations within a realm
|
||||||
|
//this way, all organizations in the realm will be invalidated ... or should it be invalidated whole realm instead?
|
||||||
|
getAllStream().forEach(this::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addMember(OrganizationModel organization, UserModel user) {
|
||||||
|
return jpaOrgDelegate.addMember(organization, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeMember(OrganizationModel organization, UserModel member) {
|
||||||
|
return jpaOrgDelegate.removeMember(organization, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserModel> getMembersStream(OrganizationModel organization, String search, Boolean exact, Integer first, Integer max) {
|
||||||
|
return jpaOrgDelegate.getMembersStream(organization, search, exact, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getMemberById(OrganizationModel organization, String id) {
|
||||||
|
return jpaOrgDelegate.getMemberById(organization, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrganizationModel getByMember(UserModel member) {
|
||||||
|
// Return cache delegate to ensure cache invalidation during write operations
|
||||||
|
return getCacheDelegate(jpaOrgDelegate.getByMember(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isManagedMember(OrganizationModel organization, UserModel member) {
|
||||||
|
return jpaOrgDelegate.isManagedMember(organization, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addIdentityProvider(OrganizationModel organization, IdentityProviderModel identityProvider) {
|
||||||
|
boolean added = jpaOrgDelegate.addIdentityProvider(organization, identityProvider);
|
||||||
|
if (added) {
|
||||||
|
registerOrganizationInvalidation(organization.getId());
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<IdentityProviderModel> getIdentityProviders(OrganizationModel organization) {
|
||||||
|
return jpaOrgDelegate.getIdentityProviders(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeIdentityProvider(OrganizationModel organization, IdentityProviderModel identityProvider) {
|
||||||
|
boolean removed = jpaOrgDelegate.removeIdentityProvider(organization, identityProvider);
|
||||||
|
if (removed) {
|
||||||
|
registerOrganizationInvalidation(organization.getId());
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return getRealm().isOrganizationsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long count() {
|
||||||
|
return jpaOrgDelegate.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
jpaOrgDelegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerOrganizationInvalidation(String orgId) {
|
||||||
|
OrganizationAdapter adapter = managedOrganizations.get(orgId);
|
||||||
|
if (adapter != null) {
|
||||||
|
adapter.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
realmCache.registerInvalidation(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RealmModel getRealm() {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
if (realm == null) {
|
||||||
|
throw new IllegalArgumentException("Session not bound to a realm");
|
||||||
|
}
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrganizationModel getCacheDelegate(OrganizationModel model) {
|
||||||
|
return model == null ? null : getById(model.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<OrganizationModel> getCacheDelegates(Stream<OrganizationModel> backendOrganizations) {
|
||||||
|
return backendOrganizations.map(this::getCacheDelegate);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* 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.organization;
|
||||||
|
|
||||||
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.organization.OrganizationProviderFactory;
|
||||||
|
|
||||||
|
public class InfinispanOrganizationProviderFactory implements OrganizationProviderFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "infinispan";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrganizationProvider create(KeycloakSession session) {
|
||||||
|
return new InfinispanOrganizationProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
factory.register(event -> {
|
||||||
|
if (event instanceof RealmModel.IdentityProviderUpdatedEvent idpUpdatedEvent) {
|
||||||
|
registerOrganizationInvalidation(idpUpdatedEvent.getKeycloakSession(), idpUpdatedEvent.getUpdatedIdentityProvider());
|
||||||
|
}
|
||||||
|
if (event instanceof RealmModel.IdentityProviderRemovedEvent idpRemovedEvent) {
|
||||||
|
registerOrganizationInvalidation(idpRemovedEvent.getKeycloakSession(), idpRemovedEvent.getRemovedIdentityProvider());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerOrganizationInvalidation(KeycloakSession session, IdentityProviderModel idp) {
|
||||||
|
if (idp.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE) != null) {
|
||||||
|
InfinispanOrganizationProvider orgProvider = (InfinispanOrganizationProvider) session.getProvider(OrganizationProvider.class, getId());
|
||||||
|
if (orgProvider != null) {
|
||||||
|
orgProvider.registerOrganizationInvalidation(idp.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int order() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* 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.organization;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.OrganizationDomainModel;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
|
||||||
|
public class OrganizationAdapter implements OrganizationModel {
|
||||||
|
|
||||||
|
private volatile boolean invalidated;
|
||||||
|
private volatile OrganizationModel updated;
|
||||||
|
private final Supplier<OrganizationModel> modelSupplier;
|
||||||
|
private final CacheRealmProvider realmCache;
|
||||||
|
private final CachedOrganization cached;
|
||||||
|
private final OrganizationProvider delegate;
|
||||||
|
|
||||||
|
public OrganizationAdapter(CachedOrganization cached, CacheRealmProvider realmCache, OrganizationProvider delegate) {
|
||||||
|
this.cached = cached;
|
||||||
|
this.realmCache = realmCache;
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.modelSupplier = this::getOrganizationModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidate() {
|
||||||
|
invalidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrganizationModel getOrganizationModel() {
|
||||||
|
return delegate.getById(cached.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUpdated() {
|
||||||
|
if (updated != null) return true;
|
||||||
|
if (!invalidated) return false;
|
||||||
|
updated = getOrganizationModel();
|
||||||
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getDelegateForUpdate() {
|
||||||
|
if (updated == null) {
|
||||||
|
realmCache.registerInvalidation(cached.getId());
|
||||||
|
updated = modelSupplier.get();
|
||||||
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
if (isUpdated()) return updated.getId();
|
||||||
|
return cached.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
if (isUpdated()) return updated.getName() ;
|
||||||
|
return cached.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
if (isUpdated()) return updated.isEnabled();
|
||||||
|
return cached.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
if (isUpdated()) return updated.getDescription();
|
||||||
|
return cached.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
if (isUpdated()) return updated.getAttributes();
|
||||||
|
return cached.getAttributes(modelSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setAttributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<OrganizationDomainModel> getDomains() {
|
||||||
|
if (isUpdated()) return updated.getDomains();
|
||||||
|
return cached.getDomains();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDomains(Set<OrganizationDomainModel> domains) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setDomains(domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<IdentityProviderModel> getIdentityProviders() {
|
||||||
|
if (isUpdated()) return updated.getIdentityProviders();
|
||||||
|
return cached.getIdentityProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isManaged(UserModel user) {
|
||||||
|
return delegate.isManagedMember(this, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProviderFactory
|
|
@ -39,4 +39,5 @@ public interface CacheRealmProvider extends RealmProvider, ClientProvider, Clien
|
||||||
void registerRoleInvalidation(String id, String roleName, String roleContainerId);
|
void registerRoleInvalidation(String id, String roleName, String roleContainerId);
|
||||||
|
|
||||||
void registerGroupInvalidation(String id);
|
void registerGroupInvalidation(String id);
|
||||||
|
void registerInvalidation(String id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,12 +147,12 @@ public interface OrganizationProvider extends Provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param organization the organization
|
* @param organization the organization
|
||||||
* @return The identityProvider associated with a given {@code organization} or {@code null} if there is none.
|
* @return Stream of the identity providers associated with the given {@code organization}. Never returns {@code null}.
|
||||||
*/
|
*/
|
||||||
Stream<IdentityProviderModel> getIdentityProviders(OrganizationModel organization);
|
Stream<IdentityProviderModel> getIdentityProviders(OrganizationModel organization);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the link between the given {@link OrganizationModel} and identity provider associated with it if such a link exists.
|
* Removes the link between the given {@link OrganizationModel} and the identity provider associated with it if such a link exists.
|
||||||
*
|
*
|
||||||
* @param organization the organization
|
* @param organization the organization
|
||||||
* @param identityProvider the identity provider
|
* @param identityProvider the identity provider
|
|
@ -17,11 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.organization.admin.resource;
|
package org.keycloak.organization.admin.resource;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
|
@ -45,7 +42,6 @@ import org.keycloak.models.ModelValidationException;
|
||||||
import org.keycloak.models.OrganizationModel;
|
import org.keycloak.models.OrganizationModel;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
import org.keycloak.organization.utils.Organizations;
|
import org.keycloak.organization.utils.Organizations;
|
||||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
|
||||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.organization.broker;
|
package org.keycloak.testsuite.organization.broker;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -45,6 +47,7 @@ import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganizationTest {
|
public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganizationTest {
|
||||||
|
@ -783,6 +786,8 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
log.debug("Updating info on updateAccount page");
|
log.debug("Updating info on updateAccount page");
|
||||||
updateAccountInformationPage.updateAccountInformation(user.getUsername(), user.getEmail(), "Firstname", "Lastname");
|
updateAccountInformationPage.updateAccountInformation(user.getUsername(), user.getEmail(), "Firstname", "Lastname");
|
||||||
|
assertThat(appPage.getRequestType(),is(AppPage.RequestType.AUTH_RESPONSE));
|
||||||
|
|
||||||
UserRepresentation account = getUserRepresentation(user.getEmail());
|
UserRepresentation account = getUserRepresentation(user.getEmail());
|
||||||
realmsResouce().realm(bc.consumerRealmName()).users().get(account.getId()).logout();
|
realmsResouce().realm(bc.consumerRealmName()).users().get(account.getId()).logout();
|
||||||
realmsResouce().realm(bc.providerRealmName()).logoutAll();
|
realmsResouce().realm(bc.providerRealmName()).logoutAll();
|
||||||
|
|
|
@ -111,11 +111,20 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
||||||
IdentityProviderRepresentation idpTemplate = organization
|
IdentityProviderRepresentation idpTemplate = organization
|
||||||
.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||||
|
|
||||||
|
//remove Org related stuff from the template
|
||||||
|
idpTemplate.getConfig().remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||||
|
idpTemplate.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
idpTemplate.getConfig().remove(OrganizationModel.IdentityProviderRedirectMode.EMAIL_MATCH.getKey());
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
idpTemplate.setAlias("idp-" + i);
|
idpTemplate.setAlias("idp-" + i);
|
||||||
idpTemplate.setInternalId(null);
|
idpTemplate.setInternalId(null);
|
||||||
testRealm().identityProviders().create(idpTemplate).close();
|
try (Response response = testRealm().identityProviders().create(idpTemplate)) {
|
||||||
organization.identityProviders().addIdentityProvider(idpTemplate.getAlias()).close();
|
assertThat("Falied to create idp-" + i, response.getStatus(), equalTo(Status.CREATED.getStatusCode()));
|
||||||
|
}
|
||||||
|
try (Response response = organization.identityProviders().addIdentityProvider(idpTemplate.getAlias())) {
|
||||||
|
assertThat("Falied to add idp-" + i, response.getStatus(), equalTo(Status.NO_CONTENT.getStatusCode()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertEquals(6, organization.identityProviders().getIdentityProviders().size());
|
Assert.assertEquals(6, organization.identityProviders().getIdentityProviders().size());
|
||||||
|
|
Loading…
Reference in a new issue