Add support for ad-hoc policies (#42508)

Closes #42126

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-09-10 12:40:17 -03:00 committed by GitHub
parent 371e4289c3
commit 0d5dfc3eae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 407 additions and 31 deletions

View File

@ -5,8 +5,12 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
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.representations.resources.policies.ResourcePolicyRepresentation;
@ -22,4 +26,14 @@ public interface RealmResourcePolicy {
@GET
@Produces(APPLICATION_JSON)
ResourcePolicyRepresentation toRepresentation();
@Path("bind/{type}/{resourceId}")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void bind(@PathParam("type") String type, @PathParam("resourceId") String resourceId);
@Path("bind/{type}/{resourceId}")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void bind(@PathParam("type") String type, @PathParam("resourceId") String resourceId, Long milliseconds);
}

View File

@ -43,14 +43,15 @@ public class EventBasedResourcePolicyProvider implements ResourcePolicyProvider
}
protected boolean isActivationEvent(ResourcePolicyEvent event) {
List<String> events = model.getConfig().getOrDefault("events", List.of());
ResourceOperationType operation = event.getOperation();
if (events.contains(operation.name())) {
if (ResourceOperationType.AD_HOC.equals(operation)) {
return true;
}
return false;
List<String> events = model.getConfig().getOrDefault("events", List.of());
return events.contains(operation.name());
}
@Override

View File

@ -28,6 +28,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.utils.StringUtil;
import java.util.List;
@ -88,12 +89,16 @@ public class JpaResourcePolicyStateProvider implements ResourcePolicyStateProvid
}
@Override
public List<ScheduledAction> getScheduledActionsByPolicy(ResourcePolicy policy) {
public List<ScheduledAction> getScheduledActionsByPolicy(String id) {
if (StringUtil.isBlank(id)) {
return List.of();
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ResourcePolicyStateEntity> query = cb.createQuery(ResourcePolicyStateEntity.class);
Root<ResourcePolicyStateEntity> stateRoot = query.from(ResourcePolicyStateEntity.class);
Predicate byPolicy = cb.equal(stateRoot.get("policyId"), policy.getId());
Predicate byPolicy = cb.equal(stateRoot.get("policyId"), id);
query.where(byPolicy);
return em.createQuery(query).getResultStream()
@ -139,6 +144,22 @@ public class JpaResourcePolicyStateProvider implements ResourcePolicyStateProvid
}
}
@Override
public void remove(String policyId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaDelete<ResourcePolicyStateEntity> delete = cb.createCriteriaDelete(ResourcePolicyStateEntity.class);
Root<ResourcePolicyStateEntity> root = delete.from(ResourcePolicyStateEntity.class);
delete.where(cb.equal(root.get("policyId"), policyId));
int deletedCount = em.createQuery(delete).executeUpdate();
if (LOGGER.isTraceEnabled()) {
if (deletedCount > 0) {
RealmModel realm = session.getContext().getRealm();
LOGGER.tracev("Deleted {0} state records for realm {1}", deletedCount, realm.getId());
}
}
}
@Override
public void removeAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();

View File

@ -40,6 +40,12 @@ public interface ResourcePolicyStateProvider extends Provider {
*/
void remove(String policyId, String resourceId);
/**
* Removes any record identified by the specified {@code policyId}.
* @param policyId the id of the policy.
*/
void remove(String policyId);
/**
* Deletes all state records associated with the current realm bound to the session.
*/
@ -55,7 +61,15 @@ public interface ResourcePolicyStateProvider extends Provider {
List<ScheduledAction> getScheduledActionsByResource(String resourceId);
List<ScheduledAction> getScheduledActionsByPolicy(ResourcePolicy policy);
List<ScheduledAction> getScheduledActionsByPolicy(String policy);
default List<ScheduledAction> getScheduledActionsByPolicy(ResourcePolicy policy) {
if (policy == null) {
return List.of();
}
return getScheduledActionsByPolicy(policy.getId());
}
List<ScheduledAction> getDueScheduledActions(ResourcePolicy policy);

View File

@ -20,7 +20,8 @@ public enum ResourceOperationType {
ADD_FEDERATED_IDENTITY(new Class[] {FederatedIdentityCreatedEvent.class}, new Class[] {FederatedIdentityRemovedEvent.class}),
REMOVE_FEDERATED_IDENTITY(FederatedIdentityRemovedEvent.class),
GROUP_MEMBERSHIP_JOIN(GroupMemberJoinEvent.class),
ROLE_GRANTED(new Class[] {RoleGrantedEvent.class}, new Class[] {RoleRevokedEvent.class});
ROLE_GRANTED(new Class[] {RoleGrantedEvent.class}, new Class[] {RoleRevokedEvent.class}),
AD_HOC(new Class[] {});
private final List<Object> types;
private final List<Object> deactivationTypes;

View File

@ -28,6 +28,7 @@ public class ResourcePolicy {
private MultivaluedHashMap<String, String> config;
private String providerId;
private String id;
private Long notBefore;
public ResourcePolicy() {
// reflection
@ -75,4 +76,12 @@ public class ResourcePolicy {
public boolean isScheduled() {
return config != null && Boolean.parseBoolean(config.getFirstOrDefault("scheduled", "true"));
}
public Long getNotBefore() {
return notBefore;
}
public void setNotBefore(Long milliseconds) {
this.notBefore = milliseconds;
}
}

View File

@ -23,25 +23,35 @@ import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderEvent;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
public enum ResourceType {
USERS(org.keycloak.events.admin.ResourceType.USER, List.of(OperationType.CREATE), List.of(EventType.LOGIN, EventType.REGISTER));
USERS(
org.keycloak.events.admin.ResourceType.USER,
List.of(OperationType.CREATE),
List.of(EventType.LOGIN, EventType.REGISTER),
(session, id) -> session.users().getUserById(session.getContext().getRealm(), id)
);
private final org.keycloak.events.admin.ResourceType supportedAdminResourceType;
private final List<OperationType> supportedAdminOperationTypes;
private final List<EventType> supportedEventTypes;
private final BiFunction<KeycloakSession, String, ?> resourceResolver;
ResourceType(org.keycloak.events.admin.ResourceType supportedAdminResourceType,
List<OperationType> supportedAdminOperationTypes,
List<EventType> supportedEventTypes) {
List<EventType> supportedEventTypes,
BiFunction<KeycloakSession, String, ?> resourceResolver) {
this.supportedAdminResourceType = supportedAdminResourceType;
this.supportedAdminOperationTypes = supportedAdminOperationTypes;
this.supportedEventTypes = supportedEventTypes;
this.resourceResolver = resourceResolver;
}
public ResourcePolicyEvent toEvent(AdminEvent event) {
@ -84,4 +94,8 @@ public enum ResourceType {
return new ResourcePolicyEvent(this, resourceOperationType, resourceId, event);
}
public Object resolveResource(KeycloakSession session, String id) {
return resourceResolver.apply(session, id);
}
}

View File

@ -0,0 +1,8 @@
package org.keycloak.models.policy;
final class AdhocResourcePolicyEvent extends ResourcePolicyEvent {
AdhocResourcePolicyEvent(ResourceType type, String resourceId) {
super(type, ResourceOperationType.AD_HOC, resourceId, null);
}
}

View File

@ -23,6 +23,7 @@ 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.stream.Stream;
@ -154,7 +155,14 @@ public class ResourcePolicyManager {
}
private ResourceAction getFirstAction(ResourcePolicy policy) {
return getActions(policy.getId()).get(0);
ResourceAction action = getActions(policy.getId()).get(0);
Long notBefore = policy.getNotBefore();
if (notBefore != null) {
action.setAfter(notBefore);
}
return action;
}
private ResourcePolicyProvider getPolicyProvider(ResourcePolicy policy) {
@ -184,19 +192,18 @@ public class ResourcePolicyManager {
public void scheduleAllEligibleResources(ResourcePolicy policy) {
if (policy.isEnabled()) {
ResourcePolicyProvider provider = getPolicyProvider(policy);
ResourceAction firstAction = getFirstAction(policy);
provider.getEligibleResourcesForInitialAction().forEach(resourceId -> {
// TODO run each scheduling task in a separate tx as other txs might schedule an action while this is running.
this.policyStateProvider.scheduleAction(policy, firstAction, resourceId);
});
provider.getEligibleResourcesForInitialAction()
.forEach(resourceId -> processEvent(List.of(policy), new AdhocResourcePolicyEvent(ResourceType.USERS, resourceId)));
}
}
public void processEvent(ResourcePolicyEvent event) {
processEvent(getPolicies(), event);
}
public void processEvent(List<ResourcePolicy> policies, ResourcePolicyEvent event) {
List<String> currentlyAssignedPolicies = policyStateProvider.getScheduledActionsByResource(event.getResourceId())
.stream().map(ScheduledAction::policyId).toList();
List<ResourcePolicy> policies = this.getPolicies();
// iterate through the policies, and for those not yet assigned to the user check if they can be assigned
policies.stream()
@ -277,6 +284,7 @@ public class ResourcePolicyManager {
realm.getComponentsStream(policy.getId(), ResourceActionProvider.class.getName()).forEach(realm::removeComponent);
realm.removeComponent(policy);
});
policyStateProvider.remove(id);
}
public ResourcePolicy getPolicy(String id) {
@ -316,8 +324,9 @@ public class ResourcePolicyManager {
public ResourcePolicy toModel(ResourcePolicyRepresentation rep) {
MultivaluedHashMap<String, String> config = ofNullable(rep.getConfig()).orElse(new MultivaluedHashMap<>());
List<ResourcePolicyConditionRepresentation> conditions = ofNullable(rep.getConditions()).orElse(List.of());
for (ResourcePolicyConditionRepresentation condition : rep.getConditions()) {
for (ResourcePolicyConditionRepresentation condition : conditions) {
String conditionProviderId = condition.getProviderId();
config.computeIfAbsent("conditions", key -> new ArrayList<>()).add(conditionProviderId);
@ -347,4 +356,14 @@ public class ResourcePolicyManager {
return new ResourceAction(rep.getProviderId(), rep.getConfig(), subActions);
}
public void bind(ResourcePolicy policy, ResourceType type, String resourceId) {
processEvent(List.of(policy), new AdhocResourcePolicyEvent(type, resourceId));
}
public Object resolveResource(ResourceType type, String resourceId) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(type, "resourceId");
return type.resolveResource(session, resourceId);
}
}

View File

@ -2,12 +2,19 @@ package org.keycloak.realm.resources.policies.admin.resource;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.models.policy.ResourcePolicy;
import org.keycloak.models.policy.ResourcePolicyManager;
import org.keycloak.models.policy.ResourceType;
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
class RealmResourcePolicyResource {
@ -35,4 +42,21 @@ class RealmResourcePolicyResource {
public ResourcePolicyRepresentation toRepresentation() {
return manager.toRepresentation(policy);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("bind/{type}/{resourceId}")
public void bind(@PathParam("type") ResourceType type, @PathParam("resourceId") String resourceId, Long notBefore) {
Object resource = manager.resolveResource(type, resourceId);
if (resource == null) {
throw new BadRequestException("Resource with id " + resourceId + " not found");
}
if (notBefore != null) {
policy.setNotBefore(notBefore);
}
manager.bind(policy, type, resourceId);
}
}

View File

@ -0,0 +1,219 @@
package org.keycloak.tests.admin.model.policy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.Duration;
import java.util.List;
import jakarta.ws.rs.core.Response;
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.EventBasedResourcePolicyProviderFactory;
import org.keycloak.models.policy.ResourcePolicyManager;
import org.keycloak.models.policy.ResourceType;
import org.keycloak.models.policy.SetUserAttributeActionProviderFactory;
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;
import org.keycloak.testframework.util.ApiUtil;
@KeycloakIntegrationTest(config = RLMServerConfig.class)
public class AdhocPolicyTest {
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(EventBasedResourcePolicyProviderFactory.ID)
.withActions(ResourcePolicyActionRepresentation.create()
.of(SetUserAttributeActionProviderFactory.ID)
.withConfig("message", "message")
.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(SetUserAttributeActionProviderFactory.ID));
}
@Test
public void testRunAdHocScheduledPolicy() {
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
.of(EventBasedResourcePolicyProviderFactory.ID)
.withActions(ResourcePolicyActionRepresentation.create()
.of(SetUserAttributeActionProviderFactory.ID)
.after(Duration.ofDays(5))
.withConfig("message", "message")
.build())
.build()).close();
List<ResourcePolicyRepresentation> policies = managedRealm.admin().resources().policies().list();
assertThat(policies, hasSize(1));
ResourcePolicyRepresentation policy = policies.get(0);
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
String id = ApiUtil.getCreatedId(response);
managedRealm.admin().resources().policies().policy(policy.getId()).bind(ResourceType.USERS.name(), id);
}
runOnServer.run((session -> {
RealmModel realm = configureSessionContext(session);
ResourcePolicyManager manager = new ResourcePolicyManager(session);
UserModel user = session.users().getUserByUsername(realm, "alice");
manager.runScheduledActions();
assertNull(user.getAttributes().get("message"));
try {
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
manager.runScheduledActions();
user = session.users().getUserByUsername(realm, "alice");
assertNotNull(user.getAttributes().get("message"));
} finally {
Time.setOffset(0);
}
}));
}
@Test
public void testRunAdHocNonScheduledPolicy() {
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
.of(EventBasedResourcePolicyProviderFactory.ID)
.withActions(ResourcePolicyActionRepresentation.create()
.of(SetUserAttributeActionProviderFactory.ID)
.withConfig("message", "message")
.build())
.build()).close();
List<ResourcePolicyRepresentation> policies = managedRealm.admin().resources().policies().list();
assertThat(policies, hasSize(1));
ResourcePolicyRepresentation policy = policies.get(0);
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
String id = ApiUtil.getCreatedId(response);
managedRealm.admin().resources().policies().policy(policy.getId()).bind(ResourceType.USERS.name(), id);
}
runOnServer.run((session -> {
RealmModel realm = configureSessionContext(session);
ResourcePolicyManager manager = new ResourcePolicyManager(session);
UserModel user = session.users().getUserByUsername(realm, "alice");
manager.runScheduledActions();
assertNotNull(user.getAttributes().get("message"));
}));
}
@Test
public void testRunAdHocTimedScheduledPolicy() {
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
.of(EventBasedResourcePolicyProviderFactory.ID)
.withActions(ResourcePolicyActionRepresentation.create()
.of(SetUserAttributeActionProviderFactory.ID)
.withConfig("message", "message")
.build())
.build()).close();
List<ResourcePolicyRepresentation> policies = managedRealm.admin().resources().policies().list();
assertThat(policies, hasSize(1));
ResourcePolicyRepresentation policy = policies.get(0);
String id;
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
id = ApiUtil.getCreatedId(response);
managedRealm.admin().resources().policies().policy(policy.getId()).bind(ResourceType.USERS.name(), id, Duration.ofDays(5).toMillis());
}
runOnServer.run((session -> {
RealmModel realm = configureSessionContext(session);
ResourcePolicyManager manager = new ResourcePolicyManager(session);
UserModel user = session.users().getUserByUsername(realm, "alice");
manager.runScheduledActions();
assertNull(user.getAttributes().get("message"));
try {
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
manager.runScheduledActions();
user = session.users().getUserByUsername(realm, "alice");
assertNotNull(user.getAttributes().get("message"));
} finally {
user.removeAttribute("message");
Time.setOffset(0);
}
}));
managedRealm.admin().resources().policies().policy(policy.getId()).bind(ResourceType.USERS.name(), id, Duration.ofDays(10).toMillis());
runOnServer.run((session -> {
RealmModel realm = configureSessionContext(session);
ResourcePolicyManager manager = new ResourcePolicyManager(session);
UserModel user = session.users().getUserByUsername(realm, "alice");
manager.runScheduledActions();
assertNull(user.getAttributes().get("message"));
try {
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
manager.runScheduledActions();
user = session.users().getUserByUsername(realm, "alice");
assertNull(user.getAttributes().get("message"));
} finally {
Time.setOffset(0);
}
try {
Time.setOffset(Math.toIntExact(Duration.ofDays(11).toSeconds()));
manager.runScheduledActions();
user = session.users().getUserByUsername(realm, "alice");
assertNotNull(user.getAttributes().get("message"));
} 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;
}
}

View File

@ -46,15 +46,16 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.policy.DeleteUserActionProviderFactory;
import org.keycloak.models.policy.DisableUserActionProviderFactory;
import org.keycloak.models.policy.EventBasedResourcePolicyProviderFactory;
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;
import org.keycloak.models.policy.ResourcePolicyStateProvider.ScheduledAction;
import org.keycloak.models.policy.SetUserAttributeActionProviderFactory;
import org.keycloak.models.policy.UserCreationTimeResourcePolicyProviderFactory;
import org.keycloak.models.policy.UserSessionRefreshTimeResourcePolicyProviderFactory;
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
@ -121,9 +122,9 @@ public class ResourcePolicyManagementTest {
}
@Test
public void testDelete() {
public void testCreateWithNoConditions() {
List<ResourcePolicyRepresentation> expectedPolicies = ResourcePolicyRepresentation.create()
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
.of(EventBasedResourcePolicyProviderFactory.ID)
.withActions(
ResourcePolicyActionRepresentation.create().of(NotifyUserActionProviderFactory.ID)
.after(Duration.ofDays(5))
@ -131,29 +132,60 @@ public class ResourcePolicyManagementTest {
ResourcePolicyActionRepresentation.create().of(DisableUserActionProviderFactory.ID)
.after(Duration.ofDays(10))
.build()
).of(UserSessionRefreshTimeResourcePolicyProviderFactory.ID)
.withActions(
ResourcePolicyActionRepresentation.create().of(NotifyUserActionProviderFactory.ID)
.after(Duration.ofDays(5))
.build(),
ResourcePolicyActionRepresentation.create().of(DisableUserActionProviderFactory.ID)
.after(Duration.ofDays(10))
.build())
.build();
).build();
expectedPolicies.get(0).setConditions(null);
RealmResourcePolicies policies = managedRealm.admin().resources().policies();
try (Response response = policies.create(expectedPolicies)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
}
}
@Test
public void testDelete() {
RealmResourcePolicies policies = managedRealm.admin().resources().policies();
policies.create(ResourcePolicyRepresentation.create()
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
.onEvent(ResourceOperationType.CREATE.toString())
.recurring()
.withActions(
ResourcePolicyActionRepresentation.create().of(NotifyUserActionProviderFactory.ID)
.after(Duration.ofDays(5))
.build()
).of(EventBasedResourcePolicyProviderFactory.ID)
.onEvent(ResourceOperationType.LOGIN.toString())
.recurring()
.withActions(
ResourcePolicyActionRepresentation.create().of(NotifyUserActionProviderFactory.ID)
.after(Duration.ofDays(5))
.build()
).build()).close();
// create a new user - should bind the user to the policy and setup the only action in the policy
managedRealm.admin().users().create(UserConfigBuilder.create().username("testuser").email("testuser@example.com").build()).close();
List<ResourcePolicyRepresentation> actualPolicies = policies.list();
assertThat(actualPolicies, Matchers.hasSize(2));
ResourcePolicyRepresentation policy = actualPolicies.get(0);
managedRealm.admin().resources().policies().policy(policy.getId()).delete().close();
ResourcePolicyRepresentation policy = actualPolicies.stream().filter(p -> UserCreationTimeResourcePolicyProviderFactory.ID.equals(p.getProviderId())).findAny().orElse(null);
String id = policy.getId();
policies.policy(id).delete().close();
actualPolicies = policies.list();
assertThat(actualPolicies, Matchers.hasSize(1));
runOnServer.run((RunOnServer) session -> {
configureSessionContext(session);
ResourcePolicyManager manager = new ResourcePolicyManager(session);
List<ResourcePolicy> registeredPolicies = manager.getPolicies();
assertEquals(1, registeredPolicies.size());
ResourcePolicyStateProvider stateProvider = session.getKeycloakSessionFactory().getProviderFactory(ResourcePolicyStateProvider.class).create(session);
List<ScheduledAction> actions = stateProvider.getScheduledActionsByPolicy(id);
assertTrue(actions.isEmpty());
});
}
@Test