mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-06 06:49:53 -06:00
Do not cache partial results when FGAP is enabled
Closes #38705 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
@@ -16,9 +16,13 @@
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.runWithoutAuthorization;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link DefaultLazyLoader} that only fetches data once. This implementation is not thread-safe
|
||||
* and cached data is assumed to not be shared across different threads to sync state.
|
||||
@@ -37,10 +41,13 @@ public class DefaultLazyLoader<S, D> implements LazyLoader<S, D> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public D get(Supplier<S> sourceSupplier) {
|
||||
public D get(KeycloakSession session, Supplier<S> sourceSupplier) {
|
||||
if (data == null) {
|
||||
S source = sourceSupplier.get();
|
||||
data = source == null ? fallback.get() : this.loader.apply(source);
|
||||
runWithoutAuthorization(session, () -> {
|
||||
// make sure caching does not include partial results when FGAP is enabled
|
||||
S source = sourceSupplier.get();
|
||||
data = source == null ? fallback.get() : loader.apply(source);
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -134,13 +134,13 @@ public class GroupAdapter implements GroupModel {
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (isUpdated()) return updated.getFirstAttribute(name);
|
||||
return cached.getAttributes(modelSupplier).getFirst(name);
|
||||
return cached.getAttributes(keycloakSession, modelSupplier).getFirst(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
if (isUpdated()) return updated.getAttributeStream(name);
|
||||
List<String> values = cached.getAttributes(modelSupplier).get(name);
|
||||
List<String> values = cached.getAttributes(keycloakSession, modelSupplier).get(name);
|
||||
if (values == null) return Stream.empty();
|
||||
return values.stream();
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class GroupAdapter implements GroupModel {
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
if (isUpdated()) return updated.getAttributes();
|
||||
return cached.getAttributes(modelSupplier);
|
||||
return cached.getAttributes(keycloakSession, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,13 +167,13 @@ public class GroupAdapter implements GroupModel {
|
||||
public boolean hasDirectRole(RoleModel role) {
|
||||
if (isUpdated()) return updated.hasDirectRole(role);
|
||||
|
||||
return cached.getRoleMappings(modelSupplier).contains(role.getId());
|
||||
return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (isUpdated()) return updated.hasRole(role);
|
||||
if (cached.getRoleMappings(modelSupplier).contains(role.getId())) return true;
|
||||
if (cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId())) return true;
|
||||
if (getRoleMappingsStream().anyMatch(r -> r.hasRole(role))) return true;
|
||||
GroupModel parent = getParent();
|
||||
return parent != null && parent.hasRole(role);
|
||||
@@ -189,7 +189,7 @@ public class GroupAdapter implements GroupModel {
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
if (isUpdated()) return updated.getRoleMappingsStream();
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
for (String id : cached.getRoleMappings(modelSupplier)) {
|
||||
for (String id : cached.getRoleMappings(keycloakSession, modelSupplier)) {
|
||||
RoleModel roleById = keycloakSession.roles().getRoleById(realm, id);
|
||||
if (roleById == null) {
|
||||
// chance that role was removed, so just delegate to persistence and get user invalidated
|
||||
@@ -225,7 +225,7 @@ public class GroupAdapter implements GroupModel {
|
||||
public Stream<GroupModel> getSubGroupsStream() {
|
||||
if (isUpdated()) return updated.getSubGroupsStream();
|
||||
Set<GroupModel> subGroups = new HashSet<>();
|
||||
for (String id : cached.getSubGroups(modelSupplier)) {
|
||||
for (String id : cached.getSubGroups(keycloakSession, modelSupplier)) {
|
||||
GroupModel subGroup = keycloakSession.groups().getGroupById(realm, id);
|
||||
if (subGroup == null) {
|
||||
// chance that role was removed, so just delegate to persistence and get user invalidated
|
||||
@@ -259,7 +259,7 @@ public class GroupAdapter implements GroupModel {
|
||||
@Override
|
||||
public Long getSubGroupsCount() {
|
||||
if (isUpdated()) return updated.getSubGroupsCount();
|
||||
return cached.getSubGroupsCount(modelSupplier);
|
||||
return getGroupModel().getSubGroupsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* <p>A functional interface that can be used to return data {@code D} from a source {@code S} where implementations are free to define how and when
|
||||
* data is fetched from source as well how it is internally cached.
|
||||
@@ -33,8 +35,9 @@ public interface LazyLoader<S, D> {
|
||||
* Returns data from the given {@code source}. Data is only fetched from {@code source} once and only if necessary, it is
|
||||
* up to implementations to decide the momentum to actually fetch data from source.
|
||||
*
|
||||
* @param session the session
|
||||
* @param source the source from where data will be fetched.
|
||||
* @return the data from source
|
||||
*/
|
||||
D get(Supplier<S> source);
|
||||
D get(KeycloakSession session, Supplier<S> source);
|
||||
}
|
||||
|
||||
@@ -721,19 +721,19 @@ public class RealmAdapter implements CachedRealmModel {
|
||||
public OAuth2DeviceConfig getOAuth2DeviceConfig() {
|
||||
if (isUpdated())
|
||||
return updated.getOAuth2DeviceConfig();
|
||||
return cached.getOAuth2DeviceConfig(modelSupplier);
|
||||
return cached.getOAuth2DeviceConfig(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CibaConfig getCibaPolicy() {
|
||||
if (isUpdated()) return updated.getCibaPolicy();
|
||||
return cached.getCibaConfig(modelSupplier);
|
||||
return cached.getCibaConfig(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParConfig getParPolicy() {
|
||||
if (isUpdated()) return updated.getParPolicy();
|
||||
return cached.getParConfig(modelSupplier);
|
||||
return cached.getParConfig(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1610,7 +1610,7 @@ public class RealmAdapter implements CachedRealmModel {
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getDefaultClientScopesStream(boolean defaultScope) {
|
||||
if (isUpdated()) return updated.getDefaultClientScopesStream(defaultScope);
|
||||
List<String> clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(modelSupplier) : cached.getOptionalDefaultClientScopes(modelSupplier);
|
||||
List<String> clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(session, modelSupplier) : cached.getOptionalDefaultClientScopes(session, modelSupplier);
|
||||
return clientScopeIds.stream()
|
||||
.map(scope -> cacheSession.getClientScopeById(this, scope))
|
||||
.filter(Objects::nonNull);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
@@ -40,15 +41,17 @@ import java.util.stream.Stream;
|
||||
public class RoleAdapter implements RoleModel {
|
||||
|
||||
protected RoleModel updated;
|
||||
private final KeycloakSession session;
|
||||
protected CachedRole cached;
|
||||
protected RealmCacheSession cacheSession;
|
||||
protected RealmModel realm;
|
||||
protected Set<RoleModel> composites;
|
||||
private final Supplier<RoleModel> modelSupplier;
|
||||
|
||||
public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) {
|
||||
public RoleAdapter(CachedRole cached, RealmCacheSession cacheSession, RealmModel realm) {
|
||||
this.cached = cached;
|
||||
this.cacheSession = session;
|
||||
this.cacheSession = cacheSession;
|
||||
this.session = cacheSession.session;
|
||||
this.realm = realm;
|
||||
this.modelSupplier = this::getRoleModel;
|
||||
}
|
||||
@@ -215,7 +218,7 @@ public class RoleAdapter implements RoleModel {
|
||||
return updated.getFirstAttribute(name);
|
||||
}
|
||||
|
||||
return cached.getAttributes(modelSupplier).getFirst(name);
|
||||
return cached.getAttributes(session, modelSupplier).getFirst(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -224,7 +227,7 @@ public class RoleAdapter implements RoleModel {
|
||||
return updated.getAttributeStream(name);
|
||||
}
|
||||
|
||||
List<String> result = cached.getAttributes(modelSupplier).get(name);
|
||||
List<String> result = cached.getAttributes(session, modelSupplier).get(name);
|
||||
if (result == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
@@ -237,7 +240,7 @@ public class RoleAdapter implements RoleModel {
|
||||
return updated.getAttributes();
|
||||
}
|
||||
|
||||
return cached.getAttributes(modelSupplier);
|
||||
return cached.getAttributes(session, modelSupplier);
|
||||
}
|
||||
|
||||
private RoleModel getRoleModel() {
|
||||
|
||||
@@ -223,26 +223,26 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (updated != null) return updated.getFirstAttribute(name);
|
||||
return cached.getFirstAttribute(name, modelSupplier);
|
||||
return cached.getFirstAttribute(keycloakSession, name, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
if (updated != null) return updated.getAttributeStream(name);
|
||||
List<String> result = cached.getAttributes(modelSupplier).get(name);
|
||||
List<String> result = cached.getAttributes(keycloakSession, modelSupplier).get(name);
|
||||
return (result == null) ? Stream.empty() : result.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
if (updated != null) return updated.getAttributes();
|
||||
return cached.getAttributes(modelSupplier);
|
||||
return cached.getAttributes(keycloakSession, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getRequiredActionsStream() {
|
||||
if (updated != null) return updated.getRequiredActionsStream();
|
||||
return cached.getRequiredActions(modelSupplier).stream();
|
||||
return cached.getRequiredActions(keycloakSession, modelSupplier).stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -318,7 +318,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialById(String id) {
|
||||
if (updated == null) {
|
||||
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
|
||||
return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential ->
|
||||
Objects.equals(id, credential.getId()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
@@ -328,7 +328,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||
if (updated == null) {
|
||||
return cached.getStoredCredentials(modelSupplier).stream();
|
||||
return cached.getStoredCredentials(keycloakSession, modelSupplier).stream();
|
||||
}
|
||||
return super.getStoredCredentialsStream();
|
||||
}
|
||||
@@ -336,7 +336,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||
if (updated == null) {
|
||||
return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType()));
|
||||
return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType()));
|
||||
}
|
||||
return super.getStoredCredentialsByTypeStream(type);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||
if (updated == null) {
|
||||
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
|
||||
return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential ->
|
||||
Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
@@ -375,13 +375,13 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public boolean hasDirectRole(RoleModel role) {
|
||||
if (updated != null) return updated.hasDirectRole(role);
|
||||
return cached.getRoleMappings(modelSupplier).contains(role.getId());
|
||||
return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (updated != null) return updated.hasRole(role);
|
||||
return cached.getRoleMappings(modelSupplier).contains(role.getId()) ||
|
||||
return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId()) ||
|
||||
getRoleMappingsStream().anyMatch(r -> r.hasRole(role)) ||
|
||||
RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true);
|
||||
}
|
||||
@@ -396,7 +396,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
if (updated != null) return updated.getRoleMappingsStream();
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
for (String id : cached.getRoleMappings(modelSupplier)) {
|
||||
for (String id : cached.getRoleMappings(keycloakSession, modelSupplier)) {
|
||||
RoleModel roleById = keycloakSession.roles().getRoleById(realm, id);
|
||||
if (roleById == null) {
|
||||
// chance that role was removed, so just delete to persistence and get user invalidated
|
||||
@@ -423,7 +423,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
result = updated.getGroupsStream();
|
||||
} else {
|
||||
Set<GroupModel> groups = null;
|
||||
for (String id : cached.getGroups(modelSupplier)) {
|
||||
for (String id : cached.getGroups(keycloakSession, modelSupplier)) {
|
||||
GroupModel groupModel = keycloakSession.groups().getGroupById(realm, id);
|
||||
if (groupModel == null) {
|
||||
// chance that role was removed, so just delegate to persistence and get user invalidated
|
||||
@@ -468,7 +468,7 @@ public class UserAdapter implements CachedUserModel {
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
if (updated != null) return updated.isMemberOf(group);
|
||||
return cached.getGroups(modelSupplier).contains(group.getId()) || RoleUtils.isMember(getGroupsStream(), group);
|
||||
return cached.getGroups(keycloakSession, modelSupplier).contains(group.getId()) || RoleUtils.isMember(getGroupsStream(), group);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.authorization.store.ScopeStore;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
|
||||
import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
@@ -45,11 +46,13 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
private final Supplier<Policy> modelSupplier;
|
||||
protected final CachedPolicy cached;
|
||||
protected final StoreFactoryCacheSession cacheSession;
|
||||
private final KeycloakSession session;
|
||||
protected Policy updated;
|
||||
|
||||
public PolicyAdapter(CachedPolicy cached, StoreFactoryCacheSession cacheSession) {
|
||||
this.cached = cached;
|
||||
this.cacheSession = cacheSession;
|
||||
this.session = cacheSession.session;
|
||||
this.modelSupplier = this::getPolicyModel;
|
||||
}
|
||||
|
||||
@@ -58,7 +61,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
if (updated == null) {
|
||||
updated = modelSupplier.get();
|
||||
String defaultResourceType = updated.getConfig().get("defaultResourceType");
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), defaultResourceType, cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), defaultResourceType, cached.getResourceServerId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
return updated;
|
||||
@@ -106,7 +109,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.setName(name);
|
||||
}
|
||||
|
||||
@@ -150,15 +153,15 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
@Override
|
||||
public Map<String, String> getConfig() {
|
||||
if (isUpdated()) return updated.getConfig();
|
||||
return cached.getConfig(modelSupplier);
|
||||
return cached.getConfig(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Map<String, String> config) {
|
||||
getDelegateForUpdate();
|
||||
if (config.containsKey("defaultResourceType") || cached.getConfig(modelSupplier).containsKey("defaultResourceType")) {
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId());
|
||||
if (config.containsKey("defaultResourceType") || cached.getConfig(session, modelSupplier).containsKey("defaultResourceType")) {
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId());
|
||||
}
|
||||
updated.setConfig(config);
|
||||
|
||||
@@ -168,7 +171,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
public void removeConfig(String name) {
|
||||
getDelegateForUpdate();
|
||||
if (name.equals("defaultResourceType")) {
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
}
|
||||
updated.removeConfig(name);
|
||||
|
||||
@@ -178,8 +181,8 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
public void putConfig(String name, String value) {
|
||||
getDelegateForUpdate();
|
||||
if (name.equals("defaultResourceType")) {
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), value, cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), value, cached.getResourceServerId());
|
||||
}
|
||||
updated.putConfig(name, value);
|
||||
}
|
||||
@@ -207,7 +210,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
associatedPolicies = new HashSet<>();
|
||||
PolicyStore policyStore = cacheSession.getPolicyStore();
|
||||
String resourceServerId = cached.getResourceServerId();
|
||||
for (String id : cached.getAssociatedPoliciesIds(modelSupplier)) {
|
||||
for (String id : cached.getAssociatedPoliciesIds(session, modelSupplier)) {
|
||||
Policy policy = policyStore.findById(cacheSession.getResourceServerStore().findById(resourceServerId), id);
|
||||
if (policy == null) {
|
||||
// probably because the policy was removed
|
||||
@@ -228,7 +231,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
resources = new HashSet<>();
|
||||
ResourceStore resourceStore = cacheSession.getResourceStore();
|
||||
ResourceServer resourceServer = getResourceServer();
|
||||
for (String resourceId : cached.getResourcesIds(modelSupplier)) {
|
||||
for (String resourceId : cached.getResourcesIds(session, modelSupplier)) {
|
||||
Resource resource = resourceStore.findById(resourceServer, resourceId);
|
||||
cacheSession.cacheResource(resource);
|
||||
resources.add(resource);
|
||||
@@ -239,14 +242,14 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
@Override
|
||||
public void addScope(Scope scope) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.addScope(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScope(Scope scope) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.removeScope(scope);
|
||||
}
|
||||
|
||||
@@ -269,7 +272,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
getDelegateForUpdate();
|
||||
HashSet<String> resources = new HashSet<>();
|
||||
resources.add(resource.getId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.addResource(resource);
|
||||
|
||||
}
|
||||
@@ -279,7 +282,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
getDelegateForUpdate();
|
||||
HashSet<String> resources = new HashSet<>();
|
||||
resources.add(resource.getId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.removeResource(resource);
|
||||
|
||||
}
|
||||
@@ -293,7 +296,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
scopes = new HashSet<>();
|
||||
ResourceServer resourceServer = getResourceServer();
|
||||
ScopeStore scopeStore = cacheSession.getScopeStore();
|
||||
for (String scopeId : cached.getScopesIds(modelSupplier)) {
|
||||
for (String scopeId : cached.getScopesIds(session, modelSupplier)) {
|
||||
Scope scope = scopeStore.findById(resourceServer, scopeId);
|
||||
cacheSession.cacheScope(scope);
|
||||
scopes.add(scope);
|
||||
@@ -310,7 +313,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||
@Override
|
||||
public void setOwner(String owner) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
|
||||
updated.setOwner(owner);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -40,6 +41,7 @@ import java.util.stream.Collectors;
|
||||
public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
|
||||
private final Supplier<Resource> modelSupplier;
|
||||
private final KeycloakSession session;
|
||||
protected final CachedResource cached;
|
||||
protected final StoreFactoryCacheSession cacheSession;
|
||||
protected Resource updated;
|
||||
@@ -47,6 +49,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
public ResourceAdapter(CachedResource cached, StoreFactoryCacheSession cacheSession) {
|
||||
this.cached = cached;
|
||||
this.cacheSession = cacheSession;
|
||||
this.session = cacheSession.session;
|
||||
this.modelSupplier = this::getResourceModel;
|
||||
}
|
||||
|
||||
@@ -54,7 +57,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
public Resource getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
updated = modelSupplier.get();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
return updated;
|
||||
@@ -102,7 +105,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setName(name);
|
||||
}
|
||||
|
||||
@@ -115,7 +118,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
@Override
|
||||
public void setDisplayName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setDisplayName(name);
|
||||
}
|
||||
|
||||
@@ -140,13 +143,13 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
@Override
|
||||
public Set<String> getUris() {
|
||||
if (isUpdated()) return updated.getUris();
|
||||
return cached.getUris(modelSupplier);
|
||||
return cached.getUris(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUris(Set<String> uris) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.updateUris(uris);
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
@Override
|
||||
public void setType(String type) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setType(type);
|
||||
|
||||
}
|
||||
@@ -171,7 +174,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
if (isUpdated()) return updated.getScopes();
|
||||
if (scopes != null) return scopes;
|
||||
scopes = new LinkedList<>();
|
||||
for (String scopeId : cached.getScopesIds(modelSupplier)) {
|
||||
for (String scopeId : cached.getScopesIds(session, modelSupplier)) {
|
||||
scopes.add(cacheSession.getScopeStore().findById(getResourceServer(), scopeId));
|
||||
}
|
||||
return scopes = Collections.unmodifiableList(scopes);
|
||||
@@ -192,7 +195,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
@Override
|
||||
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setOwnerManagedAccess(ownerManagedAccess);
|
||||
}
|
||||
|
||||
@@ -219,21 +222,21 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
}
|
||||
}
|
||||
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.updateScopes(scopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
if (updated != null) return updated.getAttributes();
|
||||
return cached.getAttributes(modelSupplier);
|
||||
return cached.getAttributes(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSingleAttribute(String name) {
|
||||
if (updated != null) return updated.getSingleAttribute(name);
|
||||
|
||||
List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
|
||||
List<String> values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList());
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return null;
|
||||
@@ -246,7 +249,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||
public List<String> getAttribute(String name) {
|
||||
if (updated != null) return updated.getAttribute(name);
|
||||
|
||||
List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
|
||||
List<String> values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList());
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
|
||||
import org.keycloak.models.cache.infinispan.LazyLoader;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
@@ -83,8 +84,8 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
|
||||
return this.logic;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig(Supplier<Policy> policy) {
|
||||
return this.config.get(policy);
|
||||
public Map<String, String> getConfig(KeycloakSession session, Supplier<Policy> policy) {
|
||||
return this.config.get(session, policy);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -95,16 +96,16 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public Set<String> getAssociatedPoliciesIds(Supplier<Policy> policy) {
|
||||
return this.associatedPoliciesIds.get(policy);
|
||||
public Set<String> getAssociatedPoliciesIds(KeycloakSession session, Supplier<Policy> policy) {
|
||||
return this.associatedPoliciesIds.get(session, policy);
|
||||
}
|
||||
|
||||
public Set<String> getResourcesIds(Supplier<Policy> policy) {
|
||||
return this.resourcesIds.get(policy);
|
||||
public Set<String> getResourcesIds(KeycloakSession session, Supplier<Policy> policy) {
|
||||
return this.resourcesIds.get(session, policy);
|
||||
}
|
||||
|
||||
public Set<String> getScopesIds(Supplier<Policy> policy) {
|
||||
return this.scopesIds.get(policy);
|
||||
public Set<String> getScopesIds(KeycloakSession session, Supplier<Policy> policy) {
|
||||
return this.scopesIds.get(session, policy);
|
||||
}
|
||||
|
||||
public String getResourceServerId() {
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
|
||||
import org.keycloak.models.cache.infinispan.LazyLoader;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
@@ -75,8 +76,8 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public Set<String> getUris(Supplier<Resource> source) {
|
||||
return this.uris.get(source);
|
||||
public Set<String> getUris(KeycloakSession session, Supplier<Resource> source) {
|
||||
return this.uris.get(session, source);
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
@@ -99,11 +100,11 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
||||
return this.resourceServerId;
|
||||
}
|
||||
|
||||
public Set<String> getScopesIds(Supplier<Resource> source) {
|
||||
return this.scopesIds.get(source);
|
||||
public Set<String> getScopesIds(KeycloakSession session, Supplier<Resource> source) {
|
||||
return this.scopesIds.get(session, source);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getAttributes(Supplier<Resource> source) {
|
||||
return attributes.get(source);
|
||||
public Map<String, List<String>> getAttributes(KeycloakSession session, Supplier<Resource> source) {
|
||||
return attributes.get(session, source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan.entities;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.GroupModel.Type;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
|
||||
@@ -42,7 +43,6 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
|
||||
private final LazyLoader<GroupModel, MultivaluedHashMap<String, String>> attributes;
|
||||
private final LazyLoader<GroupModel, Set<String>> roleMappings;
|
||||
private final LazyLoader<GroupModel, Set<String>> subGroups;
|
||||
private final LazyLoader<GroupModel, Long> subGroupsCount;
|
||||
private final Type type;
|
||||
|
||||
public CachedGroup(Long revision, RealmModel realm, GroupModel group) {
|
||||
@@ -53,7 +53,6 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
|
||||
this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new);
|
||||
this.roleMappings = new DefaultLazyLoader<>(source -> source.getRoleMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
|
||||
this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroupsStream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet);
|
||||
this.subGroupsCount = new DefaultLazyLoader<>(GroupModel::getSubGroupsCount, () -> 0L);
|
||||
this.type = group.getType();
|
||||
}
|
||||
|
||||
@@ -61,16 +60,16 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getAttributes(Supplier<GroupModel> group) {
|
||||
return attributes.get(group);
|
||||
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<GroupModel> group) {
|
||||
return attributes.get(session, group);
|
||||
}
|
||||
|
||||
public Set<String> getRoleMappings(Supplier<GroupModel> group) {
|
||||
public Set<String> getRoleMappings(KeycloakSession session, Supplier<GroupModel> group) {
|
||||
// it may happen that groups were not loaded before so we don't actually need to invalidate entries in the cache
|
||||
if (group == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return roleMappings.get(group);
|
||||
return roleMappings.get(session, group);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -81,12 +80,8 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public Set<String> getSubGroups(Supplier<GroupModel> group) {
|
||||
return subGroups.get(group);
|
||||
}
|
||||
|
||||
public Long getSubGroupsCount(Supplier<GroupModel> group) {
|
||||
return subGroupsCount.get(group);
|
||||
public Set<String> getSubGroups(KeycloakSession session, Supplier<GroupModel> group) {
|
||||
return subGroups.get(session, group);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.keycloak.models.CibaConfig;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OAuth2DeviceConfig;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
@@ -526,16 +527,16 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
||||
return accessCodeLifespanLogin;
|
||||
}
|
||||
|
||||
public OAuth2DeviceConfig getOAuth2DeviceConfig(Supplier<RealmModel> modelSupplier) {
|
||||
return deviceConfig.get(modelSupplier);
|
||||
public OAuth2DeviceConfig getOAuth2DeviceConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
|
||||
return deviceConfig.get(session, modelSupplier);
|
||||
}
|
||||
|
||||
public CibaConfig getCibaConfig(Supplier<RealmModel> modelSupplier) {
|
||||
return cibaConfig.get(modelSupplier);
|
||||
public CibaConfig getCibaConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
|
||||
return cibaConfig.get(session, modelSupplier);
|
||||
}
|
||||
|
||||
public ParConfig getParConfig(Supplier<RealmModel> modelSupplier) {
|
||||
return parConfig.get(modelSupplier);
|
||||
public ParConfig getParConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
|
||||
return parConfig.get(session, modelSupplier);
|
||||
}
|
||||
|
||||
public int getActionTokenGeneratedByAdminLifespan() {
|
||||
@@ -702,12 +703,12 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
||||
return defaultGroups;
|
||||
}
|
||||
|
||||
public List<String> getDefaultDefaultClientScopes(Supplier<RealmModel> modelSupplier) {
|
||||
return defaultDefaultClientScopes.get(modelSupplier);
|
||||
public List<String> getDefaultDefaultClientScopes(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
|
||||
return defaultDefaultClientScopes.get(session, modelSupplier);
|
||||
}
|
||||
|
||||
public List<String> getOptionalDefaultClientScopes(Supplier<RealmModel> modelSupplier) {
|
||||
return optionalDefaultClientScopes.get(modelSupplier);
|
||||
public List<String> getOptionalDefaultClientScopes(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
|
||||
return optionalDefaultClientScopes.get(session, modelSupplier);
|
||||
}
|
||||
|
||||
public List<AuthenticationFlowModel> getAuthenticationFlowList() {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.keycloak.models.cache.infinispan.entities;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
|
||||
@@ -73,7 +74,7 @@ public class CachedRole extends AbstractRevisioned implements InRealm {
|
||||
return composites;
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getAttributes(Supplier<RoleModel> roleModel) {
|
||||
return attributes.get(roleModel);
|
||||
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<RoleModel> roleModel) {
|
||||
return attributes.get(session, roleModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan.entities;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
@@ -83,11 +84,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||
return eagerLoadedAttributes.getFirst(UserModel.USERNAME);
|
||||
}
|
||||
|
||||
public String getFirstAttribute(String name, Supplier<UserModel> userModel) {
|
||||
public String getFirstAttribute(KeycloakSession session, String name, Supplier<UserModel> userModel) {
|
||||
if(eagerLoadedAttributes.containsKey(name))
|
||||
return eagerLoadedAttributes.getFirst(name);
|
||||
else
|
||||
return this.lazyLoadedAttributes.get(userModel).getFirst(name);
|
||||
return this.lazyLoadedAttributes.get(session, userModel).getFirst(name);
|
||||
}
|
||||
|
||||
public Long getCreatedTimestamp() {
|
||||
@@ -106,16 +107,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getAttributes(Supplier<UserModel> userModel) {
|
||||
return lazyLoadedAttributes.get(userModel);
|
||||
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<UserModel> userModel) {
|
||||
return lazyLoadedAttributes.get(session, userModel);
|
||||
}
|
||||
|
||||
public Set<String> getRequiredActions(Supplier<UserModel> userModel) {
|
||||
return this.requiredActions.get(userModel);
|
||||
public Set<String> getRequiredActions(KeycloakSession session, Supplier<UserModel> userModel) {
|
||||
return this.requiredActions.get(session, userModel);
|
||||
}
|
||||
|
||||
public Set<String> getRoleMappings(Supplier<UserModel> userModel) {
|
||||
return roleMappings.get(userModel);
|
||||
public Set<String> getRoleMappings(KeycloakSession session, Supplier<UserModel> userModel) {
|
||||
return roleMappings.get(session, userModel);
|
||||
}
|
||||
|
||||
public String getFederationLink() {
|
||||
@@ -126,17 +127,17 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public Set<String> getGroups(Supplier<UserModel> userModel) {
|
||||
return groups.get(userModel);
|
||||
public Set<String> getGroups(KeycloakSession session, Supplier<UserModel> userModel) {
|
||||
return groups.get(session, userModel);
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public List<CredentialModel> getStoredCredentials(Supplier<UserModel> userModel) {
|
||||
public List<CredentialModel> getStoredCredentials(KeycloakSession session, Supplier<UserModel> userModel) {
|
||||
// clone the credential model before returning it, so that modifications don't pollute the cache
|
||||
return storedCredentials.get(userModel).stream().map(CredentialModel::shallowClone).collect(Collectors.toList());
|
||||
return storedCredentials.get(session, userModel).stream().map(CredentialModel::shallowClone).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ 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.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
@@ -80,8 +81,8 @@ public class CachedOrganization extends AbstractRevisioned implements InRealm {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getAttributes(Supplier<OrganizationModel> organizationModel) {
|
||||
return attributes.get(organizationModel);
|
||||
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<OrganizationModel> organizationModel) {
|
||||
return attributes.get(session, organizationModel);
|
||||
}
|
||||
|
||||
public Stream<OrganizationDomainModel> getDomains() {
|
||||
|
||||
@@ -107,7 +107,7 @@ public class InfinispanOrganizationProvider implements OrganizationProvider {
|
||||
} else if (managedOrganizations.containsKey(id)) {
|
||||
return managedOrganizations.get(id);
|
||||
}
|
||||
OrganizationAdapter adapter = new OrganizationAdapter(cached, () -> getDelegate(), this);
|
||||
OrganizationAdapter adapter = new OrganizationAdapter(session, cached, this::getDelegate, this);
|
||||
managedOrganizations.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
@@ -32,11 +33,13 @@ public class OrganizationAdapter implements OrganizationModel {
|
||||
private volatile boolean invalidated;
|
||||
private volatile OrganizationModel updated;
|
||||
private final Supplier<OrganizationModel> modelSupplier;
|
||||
private final KeycloakSession session;
|
||||
private final CachedOrganization cached;
|
||||
private final Supplier<OrganizationProvider> delegate;
|
||||
private final InfinispanOrganizationProvider organizationCache;
|
||||
|
||||
public OrganizationAdapter(CachedOrganization cached, Supplier<OrganizationProvider> delegate, InfinispanOrganizationProvider organizationCache) {
|
||||
public OrganizationAdapter(KeycloakSession session, CachedOrganization cached, Supplier<OrganizationProvider> delegate, InfinispanOrganizationProvider organizationCache) {
|
||||
this.session = session;
|
||||
this.cached = cached;
|
||||
this.delegate = delegate;
|
||||
this.organizationCache = organizationCache;
|
||||
@@ -136,7 +139,7 @@ public class OrganizationAdapter implements OrganizationModel {
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
if (isUpdated()) return updated.getAttributes();
|
||||
return cached.getAttributes(modelSupplier);
|
||||
return cached.getAttributes(session, modelSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,7 +44,7 @@ public class HasRolePredicate implements Predicate<Map.Entry<String, Revisioned>
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
return (value instanceof CachedRole cachedRole && cachedRole.getComposites().contains(role)) ||
|
||||
(value instanceof CachedGroup cachedGroup && cachedGroup.getRoleMappings(null).contains(role)) ||
|
||||
(value instanceof CachedGroup cachedGroup && cachedGroup.getRoleMappings(null, null).contains(role)) ||
|
||||
(value instanceof RoleQuery roleQuery && roleQuery.getRoles().contains(role)) ||
|
||||
(value instanceof CachedClient cachedClient && cachedClient.getScope().contains(role)) ||
|
||||
(value instanceof CachedClientScope cachedClientScope && cachedClientScope.getScope().contains(role));
|
||||
|
||||
@@ -22,6 +22,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
@@ -43,6 +44,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import jakarta.persistence.LockModeType;
|
||||
import org.keycloak.storage.UserStoragePrivateUtil;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static org.keycloak.common.util.CollectionUtil.collectionEquals;
|
||||
@@ -161,7 +163,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, (PartialEvaluationStorageProvider) UserStoragePrivateUtil.userLocalStorage(session), realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
@@ -186,7 +188,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.equal(root.get("parentId"), group.getId()));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, (PartialEvaluationStorageProvider) UserStoragePrivateUtil.userLocalStorage(session), realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
|
||||
public static final ResourceType GROUPS = new ResourceType(GROUPS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS, IMPERSONATE_MEMBERS));
|
||||
public static final ResourceType ROLES = new ResourceType(ROLES_RESOURCE_TYPE, Set.of(MAP_ROLE, MAP_ROLE_CLIENT_SCOPE, MAP_ROLE_COMPOSITE));
|
||||
public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP), Map.of(VIEW, Set.of(VIEW_MEMBERS), MANAGE, Set.of(MANAGE_MEMBERS), IMPERSONATE, Set.of(IMPERSONATE_MEMBERS)), GROUPS.getType());
|
||||
private static final String SKIP_EVALUATION = "kc.authz.fgap.skip";
|
||||
public static final AdminPermissionsSchema SCHEMA = new AdminPermissionsSchema();
|
||||
|
||||
private final PartialEvaluator partialEvaluator = new PartialEvaluator();
|
||||
@@ -476,4 +477,40 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
|
||||
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Disables authorization and evaluation of permissions for realm resource types when executing the given {@code runnable}
|
||||
* in the context of the given {@code session}.
|
||||
*
|
||||
* <p>This method should be used whenever a code block should be executed without any evaluation or filtering based on
|
||||
* the permissions set to a realm. For instance, when caching realm resources where access enforcement does not apply.
|
||||
*
|
||||
* @param session the session. If {@code null}, authorization is enabled when executing the code block
|
||||
* @param runnable the runnable to execute
|
||||
*/
|
||||
public static void runWithoutAuthorization(KeycloakSession session, Runnable runnable) {
|
||||
if (isSkipEvaluation(session)) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
session.setAttribute(SKIP_EVALUATION, Boolean.TRUE.toString());
|
||||
runnable.run();
|
||||
} finally {
|
||||
session.removeAttribute(SKIP_EVALUATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if authorization is disabled in the context of the given {@code session} at the moment that this method is called.
|
||||
*
|
||||
* @param session the session
|
||||
* @return {@code true} if authorization is disabled. Otherwise, returns {@code false}.
|
||||
* Otherwise, {@code false}.
|
||||
* @see AdminPermissionsSchema#runWithoutAuthorization(KeycloakSession, Runnable)
|
||||
*/
|
||||
public static boolean isSkipEvaluation(KeycloakSession session) {
|
||||
return session == null || Boolean.parseBoolean(session.getAttributeOrDefault(SKIP_EVALUATION, Boolean.FALSE.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package org.keycloak.authorization;
|
||||
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.isSkipEvaluation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -220,6 +222,10 @@ public class PartialEvaluator {
|
||||
}
|
||||
|
||||
private boolean shouldSkipPartialEvaluation(KeycloakSession session, UserModel user, ResourceType resourceType) {
|
||||
if (isSkipEvaluation(session)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1108,7 +1108,7 @@ public class UserResource {
|
||||
@QueryParam("max") Integer maxResults,
|
||||
@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {
|
||||
auth.users().requireView(user);
|
||||
return user.getGroupsStream(search, firstResult, maxResults).map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation));
|
||||
return user.getGroupsStream(search, firstResult, maxResults).filter(auth.groups()::canView).map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation));
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@@ -19,8 +19,10 @@ package org.keycloak.tests.admin.authz.fgap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.GROUPS_RESOURCE_TYPE;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.USERS_RESOURCE_TYPE;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.VIEW;
|
||||
|
||||
import java.util.List;
|
||||
@@ -40,7 +42,9 @@ import org.keycloak.representations.idm.authorization.Logic;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectAdminClient;
|
||||
import org.keycloak.testframework.annotations.InjectUser;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.realm.ManagedUser;
|
||||
import org.keycloak.testframework.util.ApiUtil;
|
||||
|
||||
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
|
||||
@@ -49,6 +53,9 @@ public class GroupResourceTypeFilteringTest extends AbstractPermissionTest {
|
||||
@InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin")
|
||||
Keycloak realmAdminClient;
|
||||
|
||||
@InjectUser(ref = "alice")
|
||||
ManagedUser userAlice;
|
||||
|
||||
@BeforeEach
|
||||
public void onBeforeEach() {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
@@ -123,21 +130,59 @@ public class GroupResourceTypeFilteringTest extends AbstractPermissionTest {
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createAllPermission(client, GROUPS_RESOURCE_TYPE, policy, Set.of(VIEW));
|
||||
|
||||
List<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1);
|
||||
List<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups("group-0", -1, -1);
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(1, search.size());
|
||||
|
||||
GroupRepresentation group = search.get(0);
|
||||
assertEquals(1, group.getSubGroups().size());
|
||||
GroupRepresentation subGroup = group.getSubGroups().get(0);
|
||||
assertEquals("subgroup-0.0", subGroup.getName());
|
||||
GroupRepresentation parentGroup = search.get(0);
|
||||
assertEquals(5, parentGroup.getSubGroups().size());
|
||||
assertEquals(5, parentGroup.getSubGroupCount());
|
||||
GroupRepresentation subGroup = parentGroup.getSubGroups().stream().filter(group -> group.getName().equals("subgroup-0.0")).findFirst().orElse(null);
|
||||
assertNotNull(subGroup);
|
||||
|
||||
UserPolicyRepresentation notMyAdminPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, subGroup.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW), notMyAdminPolicy);
|
||||
search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1);
|
||||
assertTrue(search.isEmpty());
|
||||
|
||||
List<GroupRepresentation> subGroups = realmAdminClient.realm(realm.getName()).groups().group(group.getId()).getSubGroups(-1, -1, false);
|
||||
List<GroupRepresentation> subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups(-1, -1, false);
|
||||
assertEquals(4, subGroups.size());
|
||||
assertTrue(subGroups.stream().map(GroupRepresentation::getId).noneMatch(subGroup.getId()::equals));
|
||||
search = realmAdminClient.realm(realm.getName()).groups().groups("group-0", -1, -1);
|
||||
assertFalse(search.isEmpty());
|
||||
parentGroup = search.get(0);
|
||||
assertEquals(4, parentGroup.getSubGroups().size());
|
||||
assertEquals(4, parentGroup.getSubGroupCount());
|
||||
|
||||
subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups(subGroup.getName(), true, -1, -1, true);
|
||||
assertTrue(subGroups.isEmpty());
|
||||
subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups("subgroup-0.1", true, -1, -1, true);
|
||||
assertEquals(1, subGroups.size());
|
||||
|
||||
assertEquals(5, realm.admin().groups().group(parentGroup.getId()).getSubGroups(-1, -1, false).size());
|
||||
assertEquals(5, realm.admin().groups().group(parentGroup.getId()).getSubGroups(null, false, -1, -1, false).size());
|
||||
assertEquals(5, realm.admin().groups().group(parentGroup.getId()).toRepresentation().getSubGroupCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserGroups() {
|
||||
GroupRepresentation parentGroup = realm.admin().groups().groups("group-0", -1, -1).get(0);
|
||||
GroupRepresentation subGroup = realm.admin().groups().groups("subgroup-1.0", -1, -1).get(0);
|
||||
|
||||
userAlice.admin().joinGroup(parentGroup.getId());
|
||||
userAlice.admin().joinGroup(subGroup.getId());
|
||||
|
||||
List<GroupRepresentation> groups = userAlice.admin().groups();
|
||||
assertEquals(2, groups.size());
|
||||
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, userAlice.getId(), USERS_RESOURCE_TYPE, Set.of(VIEW), policy);
|
||||
createPermission(client, subGroup.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW), policy);
|
||||
|
||||
groups = realmAdminClient.realm(realm.getName()).users().get(userAlice.getId()).groups();
|
||||
assertEquals(1, groups.size());
|
||||
|
||||
groups = userAlice.admin().groups();
|
||||
assertEquals(2, groups.size());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user