mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Add a policy condition based on user attributes
Closes #42118 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
60b29daed2
commit
58990a5544
@ -56,6 +56,13 @@ public class ResourcePolicyConditionRepresentation {
|
||||
this.config.put(key, Collections.singletonList(value));
|
||||
}
|
||||
|
||||
public void setConfig(String key, List<String> values) {
|
||||
if (this.config == null) {
|
||||
this.config = new HashMap<>();
|
||||
}
|
||||
this.config.put(key, values);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private ResourcePolicyConditionRepresentation action;
|
||||
@ -70,6 +77,16 @@ public class ResourcePolicyConditionRepresentation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withConfig(String key, List<String> value) {
|
||||
action.setConfig(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withConfig(Map<String, List<String>> config) {
|
||||
action.setConfig(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourcePolicyConditionRepresentation build() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package org.keycloak.models.policy.conditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProviderFactory;
|
||||
|
||||
public class UserAttributePolicyConditionFactory implements ResourcePolicyConditionProviderFactory<UserAttributePolicyConditionProvider> {
|
||||
|
||||
public static final String ID = "user-attribute-condition";
|
||||
|
||||
@Override
|
||||
public UserAttributePolicyConditionProvider create(KeycloakSession session, Map<String, List<String>> config) {
|
||||
return new UserAttributePolicyConditionProvider(session, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(org.keycloak.Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package org.keycloak.models.policy.conditions;
|
||||
|
||||
import static org.keycloak.common.util.CollectionUtil.collectionEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProvider;
|
||||
import org.keycloak.models.policy.ResourcePolicyEvent;
|
||||
import org.keycloak.models.policy.ResourceType;
|
||||
|
||||
public class UserAttributePolicyConditionProvider implements ResourcePolicyConditionProvider {
|
||||
|
||||
private final Map<String, List<String>> expectedAttributes;
|
||||
private final KeycloakSession session;
|
||||
|
||||
public UserAttributePolicyConditionProvider(KeycloakSession session, Map<String, List<String>> expectedAttributes) {
|
||||
this.session = session;
|
||||
this.expectedAttributes = expectedAttributes;;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(ResourcePolicyEvent event) {
|
||||
if (!ResourceType.USERS.equals(event.getResourceType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String userId = event.getResourceId();
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
UserModel user = session.users().getUserById(realm, userId);
|
||||
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String, List<String>> expected : expectedAttributes.entrySet()) {
|
||||
List<String> values = user.getAttributes().getOrDefault(expected.getKey(), List.of());
|
||||
List<String> expectedValues = expected.getValue();
|
||||
|
||||
if (!collectionEquals(expectedValues, values)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -16,4 +16,5 @@
|
||||
#
|
||||
|
||||
org.keycloak.models.policy.conditions.GroupMembershipPolicyConditionFactory
|
||||
org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory
|
||||
org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory
|
||||
org.keycloak.models.policy.conditions.UserAttributePolicyConditionFactory
|
||||
@ -8,6 +8,8 @@ import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UserConfigBuilder {
|
||||
|
||||
@ -88,6 +90,11 @@ public class UserConfigBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserConfigBuilder attributes(Map<String, List<String>> attributes) {
|
||||
rep.setAttributes(Collections.combine(rep.getAttributes(), attributes));
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserConfigBuilder federatedLink(String identityProvider, String federatedUserId, String federatedUsername) {
|
||||
FederatedIdentityRepresentation federatedIdentity = new FederatedIdentityRepresentation();
|
||||
federatedIdentity.setUserId(federatedUserId);
|
||||
|
||||
@ -3,33 +3,33 @@ package org.keycloak.tests.admin.model.policy;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.keycloak.tests.admin.model.policy.ResourcePolicyManagementTest.findEmailByRecipient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResourcePolicies;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
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.SetUserAttributeActionProviderFactory;
|
||||
import org.keycloak.models.policy.conditions.GroupMembershipPolicyConditionFactory;
|
||||
import org.keycloak.models.policy.NotifyUserActionProviderFactory;
|
||||
import org.keycloak.models.policy.ResourceOperationType;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.testframework.mail.MailServer;
|
||||
import org.keycloak.testframework.mail.annotations.InjectMailServer;
|
||||
import org.keycloak.testframework.realm.GroupConfigBuilder;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
import org.keycloak.testframework.realm.UserConfigBuilder;
|
||||
@ -48,11 +48,11 @@ public class GroupMembershipJoinPolicyTest {
|
||||
@InjectRealm(lifecycle = LifeCycle.METHOD)
|
||||
ManagedRealm managedRealm;
|
||||
|
||||
@InjectMailServer
|
||||
private MailServer mailServer;
|
||||
|
||||
@Test
|
||||
public void testEventsOnGroupMembershipJoin() {
|
||||
UPConfig upConfig = managedRealm.admin().users().userProfile().getConfiguration();
|
||||
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
|
||||
managedRealm.admin().users().userProfile().update(upConfig);
|
||||
String groupId;
|
||||
|
||||
try (Response response = managedRealm.admin().groups().add(GroupConfigBuilder.create()
|
||||
@ -69,7 +69,8 @@ public class GroupMembershipJoinPolicyTest {
|
||||
.build())
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create()
|
||||
.of(NotifyUserActionProviderFactory.ID)
|
||||
.of(SetUserAttributeActionProviderFactory.ID)
|
||||
.withConfig("attribute", "attr1")
|
||||
.after(Duration.ofDays(5))
|
||||
.build()
|
||||
).build();
|
||||
@ -87,7 +88,9 @@ public class GroupMembershipJoinPolicyTest {
|
||||
userId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
managedRealm.admin().users().get(userId).joinGroup(groupId);
|
||||
UserResource userResource = managedRealm.admin().users().get(userId);
|
||||
|
||||
userResource.joinGroup(groupId);
|
||||
|
||||
runOnServer.run((session -> {
|
||||
RealmModel realm = configureSessionContext(session);
|
||||
@ -104,11 +107,8 @@ public class GroupMembershipJoinPolicyTest {
|
||||
}
|
||||
}));
|
||||
|
||||
// Verify that the notify action was executed by checking email was sent
|
||||
MimeMessage testUserMessage = findEmailByRecipient(mailServer, "generic-user@example.com");
|
||||
assertNotNull(testUserMessage, "The first action (notify) should have sent an email.");
|
||||
|
||||
mailServer.runCleanup();
|
||||
UserRepresentation rep = userResource.toRepresentation();
|
||||
assertNotNull(rep.getAttributes().get("attribute"));
|
||||
}
|
||||
|
||||
private static RealmModel configureSessionContext(KeycloakSession session) {
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
package org.keycloak.tests.admin.model.policy;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResourcePolicies;
|
||||
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.ResourceOperationType;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.models.policy.SetUserAttributeActionProviderFactory;
|
||||
import org.keycloak.models.policy.conditions.UserAttributePolicyConditionFactory;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
||||
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.realm.UserConfigBuilder;
|
||||
import org.keycloak.testframework.remote.runonserver.InjectRunOnServer;
|
||||
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
|
||||
|
||||
@KeycloakIntegrationTest(config = RLMServerConfig.class)
|
||||
public class UserAttributePolicyConditionTest {
|
||||
|
||||
private static final String REALM_NAME = "default";
|
||||
|
||||
@InjectRunOnServer(permittedPackages = "org.keycloak.tests")
|
||||
RunOnServerClient runOnServer;
|
||||
|
||||
@InjectRealm(lifecycle = LifeCycle.METHOD)
|
||||
ManagedRealm managedRealm;
|
||||
|
||||
@BeforeEach
|
||||
public void onBefore() {
|
||||
UPConfig upConfig = managedRealm.admin().users().userProfile().getConfiguration();
|
||||
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
|
||||
managedRealm.admin().users().userProfile().update(upConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionForSingleValuedAttribute() {
|
||||
String expected = "valid";
|
||||
createPolicy(expected);
|
||||
assertUserAttribute("user-1", false);
|
||||
assertUserAttribute("user-2", false, "not-valid");
|
||||
assertUserAttribute("user-3", true, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionForMultiValuedAttribute() {
|
||||
List<String> expected = List.of("v1", "v2", "v3");
|
||||
createPolicy(expected);
|
||||
assertUserAttribute("user-1", false, "v1");
|
||||
assertUserAttribute("user-2", true, expected);
|
||||
assertUserAttribute("user-3", false, "v1", "v2", "v3", "v4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionForMultipleAttributes() {
|
||||
Map<String, List<String>> expected = Map.of("a", List.of("a1"), "b", List.of("b1"), "c", List.of("c11", "c2"));
|
||||
createPolicy(expected);
|
||||
assertUserAttribute("user-1", false, Map.of("a", List.of("a3"), "b", List.of("b1"), "c", List.of("c1", "c2")));
|
||||
assertUserAttribute("user-2", true, expected);
|
||||
assertUserAttribute("user-3", false, Map.of("a", List.of("a1"), "b", List.of("b1")));
|
||||
HashMap<String, List<String>> values = new HashMap<>(expected);
|
||||
values.put("d", List.of("d1"));
|
||||
assertUserAttribute("user-4", true, values);
|
||||
}
|
||||
|
||||
private void assertUserAttribute(String username, boolean shouldExist, String... values) {
|
||||
assertUserAttribute(username, shouldExist, Map.of("attribute", List.of(values)));
|
||||
}
|
||||
|
||||
private void assertUserAttribute(String username, boolean shouldExist, List<String> values) {
|
||||
assertUserAttribute(username, shouldExist, Map.of("attribute", values));
|
||||
}
|
||||
|
||||
private void assertUserAttribute(String username, boolean shouldExist, Map<String, List<String>> attributes) {
|
||||
managedRealm.admin().users().create(UserConfigBuilder.create()
|
||||
.username(username)
|
||||
.email(username + "@example.com")
|
||||
.attributes(attributes)
|
||||
.build()).close();
|
||||
runOnServer.run((session -> {
|
||||
RealmModel realm = configureSessionContext(session);
|
||||
|
||||
try {
|
||||
// set offset to 7 days - notify action should run now
|
||||
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
|
||||
new ResourcePolicyManager(session).runScheduledActions();
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
}
|
||||
|
||||
UserModel user = session.users().getUserByUsername(realm, username);
|
||||
assertNotNull(user);
|
||||
|
||||
if (shouldExist) {
|
||||
assertTrue(user.getAttributes().containsKey("notified"));
|
||||
} else {
|
||||
assertFalse(user.getAttributes().containsKey("notified"));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void createPolicy(String... expectedValues) {
|
||||
createPolicy(Map.of("attribute", List.of(expectedValues)));
|
||||
}
|
||||
|
||||
private void createPolicy(List<String> expectedValues) {
|
||||
createPolicy(Map.of("attribute", expectedValues));
|
||||
}
|
||||
|
||||
private void createPolicy(Map<String, List<String>> attributes) {
|
||||
List<ResourcePolicyRepresentation> expectedPolicies = ResourcePolicyRepresentation.create()
|
||||
.of(EventBasedResourcePolicyProviderFactory.ID)
|
||||
.onEvent(ResourceOperationType.CREATE.name())
|
||||
.recurring()
|
||||
.onCoditions(ResourcePolicyConditionRepresentation.create()
|
||||
.of(UserAttributePolicyConditionFactory.ID)
|
||||
.withConfig(attributes)
|
||||
.build())
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create()
|
||||
.of(SetUserAttributeActionProviderFactory.ID)
|
||||
.withConfig("notified", "true")
|
||||
.after(Duration.ofDays(5))
|
||||
.build()
|
||||
).build();
|
||||
|
||||
RealmResourcePolicies policies = managedRealm.admin().resources().policies();
|
||||
|
||||
try (Response response = policies.create(expectedPolicies)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
}
|
||||
|
||||
private static RealmModel configureSessionContext(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName(REALM_NAME);
|
||||
session.getContext().setRealm(realm);
|
||||
return realm;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user