Add support for aggregated actions

Closes #42119

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor
2025-09-04 16:29:50 -03:00
parent ad12b418b4
commit a42550d2e5
10 changed files with 426 additions and 92 deletions

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;
}));
}
}

View File

@@ -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);