mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-30 11:29:57 -06:00
Add support for aggregated actions
Closes #42119 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
package org.keycloak.representations.resources.policies;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
public class ResourcePolicyActionRepresentation {
|
||||
|
||||
@@ -16,7 +17,8 @@ public class ResourcePolicyActionRepresentation {
|
||||
|
||||
private String id;
|
||||
private String providerId;
|
||||
private Map<String, List<String>> config;
|
||||
private MultivaluedHashMap<String, String> config;
|
||||
private List<ResourcePolicyActionRepresentation> actions;
|
||||
|
||||
public ResourcePolicyActionRepresentation() {
|
||||
// reflection
|
||||
@@ -26,14 +28,19 @@ public class ResourcePolicyActionRepresentation {
|
||||
this(providerId, null);
|
||||
}
|
||||
|
||||
public ResourcePolicyActionRepresentation(String providerId, Map<String, List<String>> config) {
|
||||
this(null, providerId, config);
|
||||
public ResourcePolicyActionRepresentation(String providerId, MultivaluedHashMap<String, String> config) {
|
||||
this(null, providerId, config, null);
|
||||
}
|
||||
|
||||
public ResourcePolicyActionRepresentation(String id, String providerId, Map<String, List<String>> config) {
|
||||
public ResourcePolicyActionRepresentation(String id, String providerId, MultivaluedHashMap<String, String> config, List<ResourcePolicyActionRepresentation> actions) {
|
||||
this.id = id;
|
||||
this.providerId = providerId;
|
||||
this.config = config;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getProviderId() {
|
||||
@@ -44,25 +51,37 @@ public class ResourcePolicyActionRepresentation {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getConfig() {
|
||||
public MultivaluedHashMap<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Map<String, List<String>> config) {
|
||||
public void setConfig(MultivaluedHashMap<String, String> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(String key, String value) {
|
||||
setConfig(key, Collections.singletonList(value));
|
||||
}
|
||||
|
||||
public void setConfig(String key, List<String> values) {
|
||||
if (this.config == null) {
|
||||
this.config = new HashMap<>();
|
||||
this.config = new MultivaluedHashMap<>();
|
||||
}
|
||||
this.config.put(key, Collections.singletonList(value));
|
||||
this.config.put(key, values);
|
||||
}
|
||||
|
||||
private void setAfter(long ms) {
|
||||
setConfig(AFTER_KEY, String.valueOf(ms));
|
||||
}
|
||||
|
||||
public List<ResourcePolicyActionRepresentation> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void setActions(List<ResourcePolicyActionRepresentation> actions) {
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private ResourcePolicyActionRepresentation action;
|
||||
@@ -91,6 +110,16 @@ public class ResourcePolicyActionRepresentation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withActions(ResourcePolicyActionRepresentation... actions) {
|
||||
action.setActions(Arrays.asList(actions));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withConfig(String key, List<String> values) {
|
||||
action.setConfig(key, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourcePolicyActionRepresentation build() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
||||
@@ -28,6 +30,7 @@ public class ResourceAction implements Comparable<ResourceAction> {
|
||||
private String id;
|
||||
private String providerId;
|
||||
private MultivaluedHashMap<String, String> config;
|
||||
private List<ResourceAction> actions = List.of();
|
||||
|
||||
public ResourceAction() {
|
||||
// reflection
|
||||
@@ -37,9 +40,10 @@ public class ResourceAction implements Comparable<ResourceAction> {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public ResourceAction(String providerId, MultivaluedHashMap<String, String> config) {
|
||||
public ResourceAction(String providerId, MultivaluedHashMap<String, String> config, List<ResourceAction> actions) {
|
||||
this.providerId = providerId;
|
||||
this.config = config;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public ResourceAction(ComponentModel model) {
|
||||
@@ -96,6 +100,17 @@ public class ResourceAction implements Comparable<ResourceAction> {
|
||||
return Long.valueOf(getConfig().getFirstOrDefault(AFTER_KEY, "0"));
|
||||
}
|
||||
|
||||
public List<ResourceAction> getActions() {
|
||||
if (actions == null) {
|
||||
return List.of();
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void setActions(List<ResourceAction> actions) {
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ResourceAction other) {
|
||||
return Integer.compare(this.getPriority(), other.getPriority());
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class AggregatedActionProvider implements ResourceActionProvider {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ComponentModel model;
|
||||
private final Logger log = Logger.getLogger(AggregatedActionProvider.class);
|
||||
|
||||
public AggregatedActionProvider(KeycloakSession session, ComponentModel model) {
|
||||
this.session = session;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(List<String> userIds) {
|
||||
ResourcePolicyManager manager = new ResourcePolicyManager(session);
|
||||
List<ResourceActionProvider> actions = manager.getActionById(session, model.getId())
|
||||
.getActions().stream()
|
||||
.map(manager::getActionProvider)
|
||||
.toList();
|
||||
|
||||
for (String userId : userIds) {
|
||||
for (ResourceActionProvider action : actions) {
|
||||
try {
|
||||
action.run(List.of(userId));
|
||||
} catch (Exception e) {
|
||||
log.errorf(e, "Failed to execute action %s for user %s", model.getProviderId(), userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunnable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
public class AggregatedActionProviderFactory implements ResourceActionProviderFactory<AggregatedActionProvider> {
|
||||
|
||||
public static final String ID = "aggregated-action-provider";
|
||||
|
||||
@Override
|
||||
public AggregatedActionProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new AggregatedActionProvider(session, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceType getType() {
|
||||
return ResourceType.USERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,20 @@
|
||||
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
@@ -35,12 +41,15 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.policy.ResourcePolicyStateProvider.ScheduledAction;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
|
||||
public class ResourcePolicyManager {
|
||||
|
||||
private static final Logger log = Logger.getLogger(ResourcePolicyManager.class);
|
||||
|
||||
private ResourcePolicyStateProvider policyStateProvider;
|
||||
private final ResourcePolicyStateProvider policyStateProvider;
|
||||
|
||||
public static boolean isFeatureEnabled() {
|
||||
return Profile.isFeatureEnabled(Feature.RESOURCE_LIFECYCLE);
|
||||
@@ -101,7 +110,7 @@ public class ResourcePolicyManager {
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// get the stable IDs of the old actions
|
||||
List<ResourceAction> oldActions = getActions(policy);
|
||||
List<ResourceAction> oldActions = getActions(policy.getId());
|
||||
Set<String> oldActionIds = oldActions.stream()
|
||||
.map(ResourceAction::getId)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -126,14 +135,23 @@ public class ResourcePolicyManager {
|
||||
// assign priority based on index.
|
||||
action.setPriority(i + 1);
|
||||
|
||||
List<ResourceAction> subActions = Optional.ofNullable(action.getActions()).orElse(List.of());
|
||||
|
||||
// persist the new action component.
|
||||
addAction(policy, action);
|
||||
action = addAction(policy.getId(), action);
|
||||
|
||||
for (int j = 0; j < subActions.size(); j++) {
|
||||
ResourceAction subAction = subActions.get(j);
|
||||
// assign priority based on index.
|
||||
subAction.setPriority(j + 1);
|
||||
addAction(action.getId(), subAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceAction addAction(ResourcePolicy policy, ResourceAction action) {
|
||||
private ResourceAction addAction(String parentId, ResourceAction action) {
|
||||
RealmModel realm = getRealm();
|
||||
ComponentModel policyModel = realm.getComponent(policy.getId());
|
||||
ComponentModel policyModel = realm.getComponent(parentId);
|
||||
ComponentModel actionModel = new ComponentModel();
|
||||
|
||||
actionModel.setId(action.getId());//need to keep stable UUIDs not to break a link in state table
|
||||
@@ -151,14 +169,37 @@ public class ResourcePolicyManager {
|
||||
.map(ResourcePolicy::new).toList();
|
||||
}
|
||||
|
||||
public List<ResourceAction> getActions(ResourcePolicy policy) {
|
||||
RealmModel realm = getRealm();
|
||||
return realm.getComponentsStream(policy.getId(), ResourceActionProvider.class.getName())
|
||||
.map(ResourceAction::new).sorted().toList();
|
||||
public List<ResourceAction> getActions(String policyId) {
|
||||
return getActionsStream(policyId).toList();
|
||||
}
|
||||
|
||||
public Stream<ResourceAction> getActionsStream(String parentId) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
return realm.getComponentsStream(parentId, ResourceActionProvider.class.getName())
|
||||
.map(this::toResourceAction).sorted();
|
||||
}
|
||||
|
||||
private ResourceAction toResourceAction(ComponentModel model) {
|
||||
ResourceAction action = new ResourceAction(model);
|
||||
|
||||
action.setActions(getActions(action.getId()));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
public ResourceAction getActionById(KeycloakSession session, String id) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
ComponentModel component = realm.getComponent(id);
|
||||
|
||||
if (component == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toResourceAction(component);
|
||||
}
|
||||
|
||||
private ResourceAction getFirstAction(ResourcePolicy policy) {
|
||||
return getActions(policy).get(0);
|
||||
return getActions(policy.getId()).get(0);
|
||||
}
|
||||
|
||||
private ResourcePolicyProvider getPolicyProvider(ResourcePolicy policy) {
|
||||
@@ -167,10 +208,11 @@ public class ResourcePolicyManager {
|
||||
return (ResourcePolicyProvider) factory.create(session, getRealm().getComponent(policy.getId()));
|
||||
}
|
||||
|
||||
private ResourceActionProvider getActionProvider(ResourceAction action) {
|
||||
public ResourceActionProvider getActionProvider(ResourceAction action) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
ComponentFactory<?, ?> actionFactory = (ComponentFactory<?, ?>) session.getKeycloakSessionFactory()
|
||||
.getProviderFactory(ResourceActionProvider.class, action.getProviderId());
|
||||
return (ResourceActionProvider) actionFactory.create(session, getRealm().getComponent(action.getId()));
|
||||
return (ResourceActionProvider) actionFactory.create(session, realm.getComponent(action.getId()));
|
||||
}
|
||||
|
||||
private RealmModel getRealm() {
|
||||
@@ -238,7 +280,7 @@ public class ResourcePolicyManager {
|
||||
|
||||
// iterate through the policies, and for those not yet assigned to the user check if they can be assigned
|
||||
policies.stream()
|
||||
.filter(policy -> policy.isEnabled() && !getActions(policy).isEmpty())
|
||||
.filter(policy -> policy.isEnabled() && !getActions(policy.getId()).isEmpty())
|
||||
.forEach(policy -> {
|
||||
ResourcePolicyProvider provider = getPolicyProvider(policy);
|
||||
if (!currentlyAssignedPolicies.contains(policy.getId())) {
|
||||
@@ -254,7 +296,7 @@ public class ResourcePolicyManager {
|
||||
log.debugf("Running all actions of policy %s for resource %s based on event %s",
|
||||
policy.getId(), event.getResourceId(), event.getOperation());
|
||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), s -> {
|
||||
getActions(policy).forEach(action -> getActionProvider(action).run(List.of(event.getResourceId())));
|
||||
getActions(policy.getId()).forEach(action -> getActionProvider(action).run(List.of(event.getResourceId())));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -272,7 +314,7 @@ public class ResourcePolicyManager {
|
||||
this.getPolicies().stream().filter(ResourcePolicy::isEnabled).forEach(policy -> {
|
||||
|
||||
for (ScheduledAction scheduled : policyStateProvider.getDueScheduledActions(policy)) {
|
||||
List<ResourceAction> actions = getActions(policy);
|
||||
List<ResourceAction> actions = getActions(policy.getId());
|
||||
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
ResourceAction currentAction = actions.get(i);
|
||||
@@ -329,4 +371,54 @@ public class ResourcePolicyManager {
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
public ResourcePolicyRepresentation toRepresentation(ResourcePolicy policy) {
|
||||
ResourcePolicyRepresentation rep = new ResourcePolicyRepresentation(policy.getId(), policy.getProviderId(), policy.getConfig());
|
||||
|
||||
for (ResourceAction action : getActions(policy.getId())) {
|
||||
rep.addAction(toRepresentation(action));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
private ResourcePolicyActionRepresentation toRepresentation(ResourceAction action) {
|
||||
List<ResourcePolicyActionRepresentation> actions = action.getActions().stream().map(this::toRepresentation).toList();
|
||||
return new ResourcePolicyActionRepresentation(action.getId(), action.getProviderId(), action.getConfig(), actions);
|
||||
}
|
||||
|
||||
public ResourcePolicy toModel(ResourcePolicyRepresentation rep) {
|
||||
ResourcePolicyManager manager = new ResourcePolicyManager(session);
|
||||
MultivaluedHashMap<String, String> config = ofNullable(rep.getConfig()).orElse(new MultivaluedHashMap<>());
|
||||
|
||||
for (ResourcePolicyConditionRepresentation condition : rep.getConditions()) {
|
||||
String conditionProviderId = condition.getProviderId();
|
||||
config.computeIfAbsent("conditions", key -> new ArrayList<>()).add(conditionProviderId);
|
||||
|
||||
for (Entry<String, List<String>> configEntry : condition.getConfig().entrySet()) {
|
||||
config.put(conditionProviderId + "." + configEntry.getKey(), configEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePolicy policy = manager.addPolicy(rep.getProviderId(), config);
|
||||
List<ResourceAction> actions = new ArrayList<>();
|
||||
|
||||
for (ResourcePolicyActionRepresentation actionRep : rep.getActions()) {
|
||||
actions.add(toModel(actionRep));
|
||||
}
|
||||
|
||||
manager.updateActions(policy, actions);
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
private ResourceAction toModel(ResourcePolicyActionRepresentation rep) {
|
||||
List<ResourceAction> subActions = new ArrayList<>();
|
||||
|
||||
for (ResourcePolicyActionRepresentation subAction : ofNullable(rep.getActions()).orElse(List.of())) {
|
||||
subActions.add(toModel(subAction));
|
||||
}
|
||||
|
||||
return new ResourceAction(rep.getProviderId(), rep.getConfig(), subActions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.keycloak.realm.resources.policies.admin.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
@@ -14,13 +11,9 @@ import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.policy.ResourceAction;
|
||||
import org.keycloak.models.policy.ResourcePolicy;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
|
||||
class RealmResourcePoliciesResource {
|
||||
@@ -36,7 +29,7 @@ class RealmResourcePoliciesResource {
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response create(ResourcePolicyRepresentation rep) {
|
||||
ResourcePolicy policy = createPolicy(rep);
|
||||
ResourcePolicy policy = manager.toModel(rep);
|
||||
return Response.created(session.getContext().getUri().getRequestUriBuilder().path(policy.getId()).build()).build();
|
||||
}
|
||||
|
||||
@@ -44,7 +37,7 @@ class RealmResourcePoliciesResource {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response createAll(List<ResourcePolicyRepresentation> reps) {
|
||||
for (ResourcePolicyRepresentation policy : reps) {
|
||||
createPolicy(policy);
|
||||
manager.toModel(policy);
|
||||
}
|
||||
return Response.created(session.getContext().getUri().getRequestUri()).build();
|
||||
}
|
||||
@@ -63,41 +56,6 @@ class RealmResourcePoliciesResource {
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<ResourcePolicyRepresentation> list() {
|
||||
return manager.getPolicies().stream().map(this::toRepresentation).toList();
|
||||
}
|
||||
|
||||
private ResourcePolicy createPolicy(ResourcePolicyRepresentation rep) {
|
||||
ResourcePolicyManager manager = new ResourcePolicyManager(session);
|
||||
MultivaluedHashMap<String, String> config = Optional.ofNullable(rep.getConfig()).orElse(new MultivaluedHashMap<>());
|
||||
|
||||
for (ResourcePolicyConditionRepresentation condition : rep.getConditions()) {
|
||||
String conditionProviderId = condition.getProviderId();
|
||||
config.computeIfAbsent("conditions", key -> new ArrayList<>()).add(conditionProviderId);
|
||||
|
||||
for (Entry<String, List<String>> configEntry : condition.getConfig().entrySet()) {
|
||||
config.put(conditionProviderId + "." + configEntry.getKey(), configEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePolicy policy = manager.addPolicy(rep.getProviderId(), config);
|
||||
List<ResourceAction> actions = new ArrayList<>();
|
||||
|
||||
for (ResourcePolicyActionRepresentation actionRep : rep.getActions()) {
|
||||
actions.add(new ResourceAction(actionRep.getProviderId(), new MultivaluedHashMap<>(actionRep.getConfig())));
|
||||
}
|
||||
|
||||
manager.updateActions(policy, actions);
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
ResourcePolicyRepresentation toRepresentation(ResourcePolicy policy) {
|
||||
ResourcePolicyRepresentation rep = new ResourcePolicyRepresentation(policy.getId(), policy.getProviderId(), policy.getConfig());
|
||||
|
||||
for (ResourceAction action : manager.getActions(policy)) {
|
||||
rep.addAction(new ResourcePolicyActionRepresentation(action.getId(), action.getProviderId(), action.getConfig()));
|
||||
}
|
||||
|
||||
return rep;
|
||||
return manager.getPolicies().stream().map(manager::toRepresentation).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import org.keycloak.models.policy.ResourceAction;
|
||||
import org.keycloak.models.policy.ResourcePolicy;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
|
||||
class RealmResourcePolicyResource {
|
||||
@@ -35,16 +33,6 @@ class RealmResourcePolicyResource {
|
||||
@GET
|
||||
@Produces(APPLICATION_JSON)
|
||||
public ResourcePolicyRepresentation toRepresentation() {
|
||||
return toRepresentation(policy);
|
||||
}
|
||||
|
||||
ResourcePolicyRepresentation toRepresentation(ResourcePolicy policy) {
|
||||
ResourcePolicyRepresentation rep = new ResourcePolicyRepresentation(policy.getId(), policy.getProviderId(), policy.getConfig());
|
||||
|
||||
for (ResourceAction action : manager.getActions(policy)) {
|
||||
rep.addAction(new ResourcePolicyActionRepresentation(action.getId(), action.getProviderId(), action.getConfig()));
|
||||
}
|
||||
|
||||
return rep;
|
||||
return manager.toRepresentation(policy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,5 @@ org.keycloak.models.policy.DisableUserActionProviderFactory
|
||||
org.keycloak.models.policy.NotifyUserActionProviderFactory
|
||||
org.keycloak.models.policy.DeleteUserActionProviderFactory
|
||||
org.keycloak.models.policy.SetUserAttributeActionProviderFactory
|
||||
org.keycloak.models.policy.AggregatedActionProviderFactory
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.keycloak.tests.admin.model.policy;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
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 java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.policy.AggregatedActionProviderFactory;
|
||||
import org.keycloak.models.policy.DisableUserActionProviderFactory;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.models.policy.SetUserAttributeActionProviderFactory;
|
||||
import org.keycloak.models.policy.UserCreationTimeResourcePolicyProviderFactory;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
import org.keycloak.testframework.remote.runonserver.InjectRunOnServer;
|
||||
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
|
||||
|
||||
@KeycloakIntegrationTest(config = RLMServerConfig.class)
|
||||
public class AggregatedActionTest {
|
||||
|
||||
private static final String REALM_NAME = "default";
|
||||
|
||||
@InjectRunOnServer(permittedPackages = "org.keycloak.tests")
|
||||
RunOnServerClient runOnServer;
|
||||
|
||||
@InjectRealm(lifecycle = LifeCycle.METHOD)
|
||||
ManagedRealm managedRealm;
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create().of(AggregatedActionProviderFactory.ID)
|
||||
.after(Duration.ofDays(5))
|
||||
.withActions(ResourcePolicyActionRepresentation.create()
|
||||
.of(SetUserAttributeActionProviderFactory.ID)
|
||||
.withConfig("message", "message")
|
||||
.build(),
|
||||
ResourcePolicyActionRepresentation.create()
|
||||
.of(DisableUserActionProviderFactory.ID)
|
||||
.build()
|
||||
).build())
|
||||
.build()).close();
|
||||
|
||||
List<ResourcePolicyRepresentation> policies = managedRealm.admin().resources().policies().list();
|
||||
assertThat(policies, hasSize(1));
|
||||
ResourcePolicyRepresentation policy = policies.get(0);
|
||||
assertThat(policy.getActions(), hasSize(1));
|
||||
ResourcePolicyActionRepresentation aggregatedAction = policy.getActions().get(0);
|
||||
assertThat(aggregatedAction.getProviderId(), is(AggregatedActionProviderFactory.ID));
|
||||
List<ResourcePolicyActionRepresentation> actions = aggregatedAction.getActions();
|
||||
assertThat(actions, hasSize(2));
|
||||
assertAction(actions, SetUserAttributeActionProviderFactory.ID, a -> {
|
||||
assertNotNull(a.getConfig());
|
||||
assertThat(a.getConfig().isEmpty(), is(false));
|
||||
assertThat(a.getConfig(), hasEntry("priority", List.of("1")));
|
||||
assertThat(a.getConfig(), hasEntry("message", List.of("message")));
|
||||
});
|
||||
assertAction(actions, DisableUserActionProviderFactory.ID, a -> {
|
||||
assertNotNull(a.getConfig());
|
||||
assertThat(a.getConfig().isEmpty(), is(false));
|
||||
assertThat(a.getConfig(), hasEntry("priority", List.of("2")));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionRun() {
|
||||
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create().of(AggregatedActionProviderFactory.ID)
|
||||
.after(Duration.ofDays(5))
|
||||
.withActions(ResourcePolicyActionRepresentation.create()
|
||||
.of(SetUserAttributeActionProviderFactory.ID)
|
||||
.withConfig("message", "message")
|
||||
.build(),
|
||||
ResourcePolicyActionRepresentation.create()
|
||||
.of(DisableUserActionProviderFactory.ID)
|
||||
.build()
|
||||
).build())
|
||||
.build()).close();
|
||||
|
||||
managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org")).close();
|
||||
|
||||
runOnServer.run((session -> {
|
||||
RealmModel realm = configureSessionContext(session);
|
||||
ResourcePolicyManager manager = new ResourcePolicyManager(session);
|
||||
|
||||
try {
|
||||
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
|
||||
manager.runScheduledActions();
|
||||
UserModel user = session.users().getUserByUsername(realm, "alice");
|
||||
assertNotNull(user.getAttributes().get("message"));
|
||||
assertFalse(user.isEnabled());
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static RealmModel configureSessionContext(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName(REALM_NAME);
|
||||
session.getContext().setRealm(realm);
|
||||
return realm;
|
||||
}
|
||||
|
||||
private UserRepresentation getUserRepresentation(String username, String firstName, String lastName, String email) {
|
||||
UserRepresentation representation = new UserRepresentation();
|
||||
representation.setUsername(username);
|
||||
representation.setFirstName(firstName);
|
||||
representation.setLastName(lastName);
|
||||
representation.setEmail(email);
|
||||
representation.setEnabled(true);
|
||||
CredentialRepresentation credential = new CredentialRepresentation();
|
||||
credential.setType(CredentialRepresentation.PASSWORD);
|
||||
credential.setValue(username);
|
||||
representation.setCredentials(List.of(credential));
|
||||
return representation;
|
||||
}
|
||||
|
||||
private void assertAction(List<ResourcePolicyActionRepresentation> actions, String expectedProviderId, Consumer<ResourcePolicyActionRepresentation> assertions) {
|
||||
assertTrue(actions.stream()
|
||||
.anyMatch(a -> {
|
||||
if (a.getProviderId().equals(expectedProviderId)) {
|
||||
assertions.accept(a);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -233,8 +233,8 @@ public class ResourcePolicyManagementTest {
|
||||
assertEquals(1, registeredPolicies.size());
|
||||
|
||||
ResourcePolicy policy = registeredPolicies.get(0);
|
||||
assertEquals(2, manager.getActions(policy).size());
|
||||
ResourceAction notifyAction = manager.getActions(policy).get(0);
|
||||
assertEquals(2, manager.getActions(policy.getId()).size());
|
||||
ResourceAction notifyAction = manager.getActions(policy.getId()).get(0);
|
||||
|
||||
ResourcePolicyStateProvider stateProvider = session.getProvider(ResourcePolicyStateProvider.class);
|
||||
ResourcePolicyStateProvider.ScheduledAction scheduledAction = stateProvider.getScheduledAction(policy.getId(), user.getId());
|
||||
@@ -249,7 +249,7 @@ public class ResourcePolicyManagementTest {
|
||||
user = session.users().getUserById(realm, user.getId());
|
||||
|
||||
// Verify that the next action was scheduled for the user
|
||||
ResourceAction disableAction = manager.getActions(policy).get(1);
|
||||
ResourceAction disableAction = manager.getActions(policy.getId()).get(1);
|
||||
scheduledAction = stateProvider.getScheduledAction(policy.getId(), user.getId());
|
||||
assertNotNull(scheduledAction, "An action should have been scheduled for the user " + user.getUsername());
|
||||
assertEquals(disableAction.getId(), scheduledAction.actionId(), "The second action should have been scheduled");
|
||||
@@ -313,8 +313,8 @@ public class ResourcePolicyManagementTest {
|
||||
assertEquals(1, registeredPolicies.size());
|
||||
ResourcePolicy policy = registeredPolicies.get(0);
|
||||
|
||||
assertEquals(2, policyManager.getActions(policy).size());
|
||||
ResourceAction notifyAction = policyManager.getActions(policy).get(0);
|
||||
assertEquals(2, policyManager.getActions(policy.getId()).size());
|
||||
ResourceAction notifyAction = policyManager.getActions(policy.getId()).get(0);
|
||||
|
||||
// check no policies are yet attached to the previous users, only to the ones created after the policy was in place
|
||||
ResourcePolicyStateProvider stateProvider = session.getKeycloakSessionFactory().getProviderFactory(ResourcePolicyStateProvider.class).create(session);
|
||||
@@ -333,7 +333,7 @@ public class ResourcePolicyManagementTest {
|
||||
policyManager.runScheduledActions();
|
||||
|
||||
// check the same users are now scheduled to run the second action.
|
||||
ResourceAction disableAction = policyManager.getActions(policy).get(1);
|
||||
ResourceAction disableAction = policyManager.getActions(policy.getId()).get(1);
|
||||
scheduledActions = stateProvider.getScheduledActionsByPolicy(policy);
|
||||
assertEquals(3, scheduledActions.size());
|
||||
scheduledActions.forEach(scheduledAction -> {
|
||||
@@ -516,7 +516,7 @@ public class ResourcePolicyManagementTest {
|
||||
|
||||
UserModel user = session.users().getUserByUsername(realm, "testuser");
|
||||
ResourcePolicy policy = manager.getPolicies().get(0);
|
||||
ResourceAction action = manager.getActions(policy).get(0);
|
||||
ResourceAction action = manager.getActions(policy.getId()).get(0);
|
||||
|
||||
// Verify that the action was scheduled again for the user
|
||||
ResourcePolicyStateProvider stateProvider = session.getProvider(ResourcePolicyStateProvider.class);
|
||||
|
||||
Reference in New Issue
Block a user