mirror of
https://github.com/keycloak/keycloak.git
synced 2026-06-09 00:52:07 -04:00
Refactoring built-in policies to use conditions
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
03cbc11e7e
commit
cee9b6803b
4 changed files with 93 additions and 61 deletions
|
|
@ -17,13 +17,6 @@
|
|||
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import jakarta.persistence.criteria.Subquery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -31,14 +24,17 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import jakarta.persistence.criteria.Subquery;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionProvider;
|
||||
|
|
@ -76,57 +72,32 @@ public abstract class AbstractUserResourcePolicyProvider implements ResourcePoli
|
|||
Predicate notExistsPredicate = cb.not(cb.exists(subquery));
|
||||
predicates.add(notExistsPredicate);
|
||||
|
||||
// origin-based condition
|
||||
Predicate originPredicate = buildOriginPredicate(cb, query, userRoot);
|
||||
if (originPredicate != null) {
|
||||
predicates.add(originPredicate);
|
||||
}
|
||||
|
||||
// todo: add relationship predicates (groups, roles, perhaps user attribute?)
|
||||
predicates.addAll(getConditionsPredicate(cb, query, userRoot));
|
||||
|
||||
query.select(userRoot.get("id")).where(predicates);
|
||||
|
||||
return em.createQuery(query).getResultList();
|
||||
}
|
||||
|
||||
protected Predicate buildOriginPredicate(CriteriaBuilder cb, CriteriaQuery<String> query, Root<UserEntity> userRoot) {
|
||||
// As of now we check only if there are broker aliases configured for the policy. More complete approach should check
|
||||
// a "origin" attribute for the origin type, and add the predicate accordingly
|
||||
// e.g. "origin" = "broker", check broker aliases; "origin" = "any" no need to filter anything; "origin" = "fed-provider", check "fed-providers", etc
|
||||
if (!this.getBrokerAliases().isEmpty()) {
|
||||
Subquery<Integer> subquery = query.subquery(Integer.class);
|
||||
Root<FederatedIdentityEntity> from = subquery.from(FederatedIdentityEntity.class);
|
||||
private List<Predicate> getConditionsPredicate(CriteriaBuilder cb, CriteriaQuery<String> query, Root<UserEntity> path) {
|
||||
List<String> conditions = policyModel.getConfig().getOrDefault("conditions", List.of());
|
||||
|
||||
subquery.select(cb.literal(1));
|
||||
subquery.where(
|
||||
cb.and(
|
||||
cb.equal(from.get("user").get("id"), userRoot.get("id")),
|
||||
from.get("identityProvider").in(getBrokerAliases())
|
||||
)
|
||||
);
|
||||
return cb.exists(subquery);
|
||||
if (conditions.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the specified resource is in the scope of this policy. For example, a policy associated with a
|
||||
* broker is applicable only to users with a federated identity associated with the same broker.
|
||||
*
|
||||
* @param resourceId the id of the resource being checked.
|
||||
* @return {@code true} if the resource is in the policy scope; {@code false} otherwise.
|
||||
*/
|
||||
protected boolean isResourceInScope(String resourceId) {
|
||||
UserModel user = this.getSession().users().getUserById(this.getRealm(), resourceId);
|
||||
if (user != null) {
|
||||
List<String> brokerAliases = this.getBrokerAliases();
|
||||
if (!brokerAliases.isEmpty()) {
|
||||
return session.users().getFederatedIdentitiesStream(this.getRealm(), user)
|
||||
.map(FederatedIdentityModel::getIdentityProvider)
|
||||
.anyMatch(brokerAliases::contains);
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
for (String providerId : conditions) {
|
||||
ResourcePolicyConditionProvider condition = resolveCondition(providerId);
|
||||
Predicate predicate = condition.toPredicate(cb, query, path);
|
||||
|
||||
if (predicate != null) {
|
||||
predicates.add(predicate);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return predicates;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -136,22 +107,51 @@ public abstract class AbstractUserResourcePolicyProvider implements ResourcePoli
|
|||
|
||||
@Override
|
||||
public boolean activateOnEvent(ResourcePolicyEvent event) {
|
||||
return this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForActivation().contains(event.getOperation())
|
||||
&& this.isResourceInScope(event.getResourceId());
|
||||
boolean b = this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForActivation().contains(event.getOperation());
|
||||
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return evaluate(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetOnEvent(ResourcePolicyEvent event) {
|
||||
return this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForResetting().contains(event.getOperation())
|
||||
&& this.isResourceInScope(event.getResourceId());
|
||||
boolean b = this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForResetting().contains(event.getOperation());
|
||||
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return evaluate(event);
|
||||
}
|
||||
|
||||
public boolean deactivateOnEvent(ResourcePolicyEvent event) {
|
||||
return this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForDeactivation().contains(event.getOperation())
|
||||
&& !this.isResourceInScope(event.getResourceId());
|
||||
boolean b = this.supports(event.getResourceType())
|
||||
&& this.getSupportedOperationsForDeactivation().contains(event.getOperation());
|
||||
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !evaluate(event);
|
||||
}
|
||||
|
||||
private boolean evaluate(ResourcePolicyEvent event) {
|
||||
List<String> conditions = policyModel.getConfig().getOrDefault("conditions", List.of());
|
||||
|
||||
for (String providerId : conditions) {
|
||||
ResourcePolicyConditionProvider condition = resolveCondition(providerId);
|
||||
|
||||
if (!condition.evaluate(event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,11 +13,17 @@ package org.keycloak.models.policy.conditions;
|
|||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import jakarta.persistence.criteria.Subquery;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProvider;
|
||||
import org.keycloak.models.policy.ResourcePolicyEvent;
|
||||
import org.keycloak.models.policy.ResourceType;
|
||||
|
|
@ -48,6 +54,22 @@ public class IdentityProviderPolicyConditionProvider implements ResourcePolicyCo
|
|||
.anyMatch(expectedAliases::contains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(CriteriaBuilder cb, CriteriaQuery<String> query, Root<?> path) {
|
||||
Subquery<Integer> subquery = query.subquery(Integer.class);
|
||||
Root<FederatedIdentityEntity> from = subquery.from(FederatedIdentityEntity.class);
|
||||
|
||||
subquery.select(cb.literal(1));
|
||||
subquery.where(
|
||||
cb.and(
|
||||
cb.equal(from.get("user").get("id"), path.get("id")),
|
||||
from.get("identityProvider").in(expectedAliases)
|
||||
)
|
||||
);
|
||||
|
||||
return cb.exists(subquery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,17 @@
|
|||
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface ResourcePolicyConditionProvider extends Provider {
|
||||
|
||||
boolean evaluate(ResourcePolicyEvent event);
|
||||
|
||||
default Predicate toPredicate(CriteriaBuilder cb, CriteriaQuery<String> query, Root<?> userRoot) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.policy.DisableUserActionProviderFactory;
|
||||
import org.keycloak.models.policy.NotifyUserActionProviderFactory;
|
||||
import org.keycloak.models.policy.ResourceAction;
|
||||
import org.keycloak.models.policy.ResourceOperationType;
|
||||
import org.keycloak.models.policy.ResourcePolicy;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.models.policy.ResourcePolicyStateProvider;
|
||||
|
|
@ -260,6 +261,7 @@ public class ResourcePolicyManagementTest {
|
|||
|
||||
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
|
||||
.onEvent(ResourceOperationType.ADD_FEDERATED_IDENTITY.name())
|
||||
.onCoditions(ResourcePolicyConditionRepresentation.create()
|
||||
.of(IdentityProviderPolicyConditionFactory.ID)
|
||||
.withConfig(IdentityProviderPolicyConditionFactory.EXPECTED_ALIASES, "someidp")
|
||||
|
|
|
|||
Loading…
Reference in a new issue