mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Add support for ad-hoc policies (#42508)
Closes #42126 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
371e4289c3
commit
0d5dfc3eae
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user