diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
index 22c81198164..5fc43a34f5f 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
@@ -50,8 +50,9 @@
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
public class AdminPermissionsSchema extends AuthorizationSchema {
-
+
public static final String USERS_RESOURCE_TYPE = "Users";
+ public static final String CLIENTS_RESOURCE_TYPE = "Clients";
//scopes
public static final String MANAGE = "manage";
@@ -59,13 +60,17 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
public static final String IMPERSONATE = "impersonate";
public static final String MAP_ROLES = "map-roles";
public static final String MANAGE_GROUP_MEMBERSHIP = "manage-group-membership";
+ public static final String CONFIGURE = "configure";
+ public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope";
+ public static final String MAP_ROLES_COMPOSITE = "map-roles-composite";
public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP));
+ public static final ResourceType CLIENTS = new ResourceType(CLIENTS_RESOURCE_TYPE, Set.of(CONFIGURE, MANAGE, MAP_ROLES, MAP_ROLES_CLIENT_SCOPE, MAP_ROLES_COMPOSITE, VIEW));
public static final AdminPermissionsSchema SCHEMA = new AdminPermissionsSchema();
private AdminPermissionsSchema() {
- super(Map.of(USERS_RESOURCE_TYPE, USERS));
+ super(Map.of(USERS_RESOURCE_TYPE, USERS, CLIENTS_RESOURCE_TYPE, CLIENTS));
}
public Resource getOrCreateResource(KeycloakSession session, ResourceServer resourceServer, String policyType, String resourceType, String id) {
@@ -85,6 +90,8 @@ public Resource getOrCreateResource(KeycloakSession session, ResourceServer reso
if (USERS.getType().equals(resourceType)) {
name = resolveUser(session, id);
+ } else if (CLIENTS.getType().equals(resourceType)) {
+ name = resolveClient(session, id);
}
if (name == null) {
@@ -165,6 +172,17 @@ private String resolveUser(KeycloakSession session, String id) {
return user == null ? null : user.getId();
}
+ private String resolveClient(KeycloakSession session, String id) {
+ RealmModel realm = session.getContext().getRealm();
+ ClientModel client = session.clients().getClientById(realm, id);
+
+ if (client == null) {
+ client = session.clients().getClientByClientId(realm, id);
+ }
+
+ return client == null ? null : client.getId();
+ }
+
private StoreFactory getStoreFactory(KeycloakSession session) {
AuthorizationProvider authzProvider = session.getProvider(AuthorizationProvider.class);
return authzProvider.getStoreFactory();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
index d40a7f4e712..08da4663b98 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
@@ -74,23 +74,25 @@ public static void registerListener(ProviderEventManager manager) {
manager.register(new ProviderEventListener() {
@Override
public void onEvent(ProviderEvent event) {
- if (event instanceof RoleContainerModel.RoleRemovedEvent) {
- RoleContainerModel.RoleRemovedEvent cast = (RoleContainerModel.RoleRemovedEvent)event;
- RoleModel role = cast.getRole();
- RealmModel realm;
- if (role.getContainer() instanceof ClientModel) {
- realm = ((ClientModel)role.getContainer()).getRealm();
+ if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
+ if (event instanceof RoleContainerModel.RoleRemovedEvent) {
+ RoleContainerModel.RoleRemovedEvent cast = (RoleContainerModel.RoleRemovedEvent) event;
+ RoleModel role = cast.getRole();
+ RealmModel realm;
+ if (role.getContainer() instanceof ClientModel) {
+ realm = ((ClientModel) role.getContainer()).getRealm();
- } else {
- realm = (RealmModel)role.getContainer();
+ } else {
+ realm = (RealmModel) role.getContainer();
+ }
+ management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
+ } else if (event instanceof ClientModel.ClientRemovedEvent) {
+ ClientModel.ClientRemovedEvent cast = (ClientModel.ClientRemovedEvent) event;
+ management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
+ } else if (event instanceof GroupModel.GroupRemovedEvent) {
+ GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent) event;
+ management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
}
- management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
- } else if (event instanceof ClientModel.ClientRemovedEvent) {
- ClientModel.ClientRemovedEvent cast = (ClientModel.ClientRemovedEvent)event;
- management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
- } else if (event instanceof GroupModel.GroupRemovedEvent) {
- GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent)event;
- management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
}
}
});
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
index ad3f94f9000..c5fbe70f962 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.services.resources.admin.permissions;
+import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
@@ -31,54 +32,161 @@ public interface ClientPermissionEvaluator {
void setPermissionsEnabled(ClientModel client, boolean enable);
+ /**
+ * Throws ForbiddenException if {@link #canListClientScopes()} returns {@code false}.
+ */
void requireListClientScopes();
+ /**
+ * Returns {@code true} if the caller has {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} role.
+ *
+ * For V2 only: Also if it has permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MANAGE}.
+ */
boolean canManage();
+ /**
+ * Throws ForbiddenException if {@link #canManage()} returns {@code false}.
+ */
void requireManage();
+ /**
+ * Returns {@code true} if the caller has {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} role.
+ *
+ * For V2 only: Also if it has permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MANAGE}.
+ */
boolean canManageClientScopes();
+ /**
+ * Throws ForbiddenException if {@link #canManageClientScopes()} returns {@code false}.
+ */
void requireManageClientScopes();
+ /**
+ * Returns {@code true} if the caller has at least one of the {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} or {@link org.keycloak.models.AdminRoles#VIEW_CLIENTS} roles.
+ *
+ * For V2 only: Also if it has permission to {@link org.keycloak.authorization.AdminPermissionsSchema#VIEW}.
+ */
boolean canView();
+ /**
+ * Returns {@code true} if {@link #canView()} returns {@code true}.
+ *
+ * Or if the caller has at least one of the {@link AdminRoles#QUERY_CLIENTS} or {@link AdminRoles#QUERY_USERS} roles.
+ */
boolean canList();
+ /**
+ * Returns {@code true} if {@link #canView()} returns {@code true}.
+ */
boolean canViewClientScopes();
+ /**
+ * Throws ForbiddenException if {@link #canList()} returns {@code false}.
+ */
void requireList();
+ /**
+ * Returns {@code true} if {@link #canView()} returns {@code true}.
+ *
+ * Or if the caller has {@link AdminRoles#QUERY_CLIENTS} role.
+ */
boolean canListClientScopes();
+ /**
+ * Returns {@code true} if {@link #canView()} returns {@code true}.
+ */
void requireView();
+ /**
+ * Returns {@code true} if {@link #canViewClientScopes()} returns {@code true}.
+ */
void requireViewClientScopes();
+ /**
+ * Returns {@code true} if the caller has {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} role.
+ *
+ * Or if the caller has a permission to {@link AdminPermissionManagement#MANAGE_SCOPE} the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MANAGE} all clients.
+ */
boolean canManage(ClientModel client);
+ /**
+ * Returns {@code true} if {@link #canManage(ClientModel)} returns {@code true}.
+ *
+ * Or if the caller has a permission to {@link ClientPermissionManagement#CONFIGURE_SCOPE} the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#CONFIGURE} all clients.
+ */
boolean canConfigure(ClientModel client);
+ /**
+ * Throws ForbiddenException if {@link #canConfigure(ClientModel)} returns {@code false}.
+ */
void requireConfigure(ClientModel client);
+ /**
+ * Throws ForbiddenException if {@link #canManage(ClientModel)} returns {@code false}.
+ */
void requireManage(ClientModel client);
+ /**
+ * Returns {@code true} if {@link #canView()} or {@link #canConfigure(ClientModel)} returns {@code true}.
+ *
+ * Or if the caller has a permission to {@link AdminPermissionManagement#VIEW_SCOPE} the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#VIEW} all clients.
+ */
boolean canView(ClientModel client);
+ /**
+ * Throws ForbiddenException if {@link #canView(ClientModel)} returns {@code false}.
+ */
void requireView(ClientModel client);
+ /**
+ * Returns {@code true} if the caller has {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} role.
+ *
+ * For V2 only: Also if it has permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MANAGE}.
+ */
boolean canManage(ClientScopeModel clientScope);
+ /**
+ * Throws ForbiddenException if {@link #canManage(ClientScopeModel)} returns {@code false}.
+ */
void requireManage(ClientScopeModel clientScope);
+ /**
+ * Returns {@code true} if the caller has at least one of the {@link org.keycloak.models.AdminRoles#VIEW_CLIENTS} or {@link org.keycloak.models.AdminRoles#MANAGE_CLIENTS} roles.
+ *
+ * For V2 only: Also if it has permission to {@link org.keycloak.authorization.AdminPermissionsSchema#VIEW} or {@link org.keycloak.authorization.AdminPermissionsSchema#MANAGE}.
+ */
boolean canView(ClientScopeModel clientScope);
+ /**
+ * Throws ForbiddenException if {@link #canView(ClientScopeModel)} returns {@code false}.
+ */
void requireView(ClientScopeModel clientScope);
+ /**
+ * Returns {@code true} if the caller has a permission to {@link ClientPermissionManagement#MAP_ROLES_SCOPE} for the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MAP_ROLES} for all clients.
+ */
boolean canMapRoles(ClientModel client);
+ /**
+ * Returns {@code true} if the caller has a permission to {@link ClientPermissionManagement#MAP_ROLES_COMPOSITE_SCOPE} for the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MAP_ROLES_COMPOSITE} for all clients.
+ */
boolean canMapCompositeRoles(ClientModel client);
+ /**
+ * Returns {@code true} if the caller has a permission to {@link ClientPermissionManagement#MAP_ROLES_CLIENT_SCOPE} for the client.
+ *
+ * For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#MAP_ROLES_CLIENT_SCOPE} for all clients.
+ */
boolean canMapClientScopeRoles(ClientModel client);
Map getAccess(ClientModel client);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java
new file mode 100644
index 00000000000..a751293df73
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authorization.AdminPermissionsSchema;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.common.ClientModelIdentity;
+import org.keycloak.authorization.common.DefaultEvaluationContext;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientScopeModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.authorization.Permission;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.TOKEN_EXCHANGE;
+
+
+public class ClientPermissionsV2 extends ClientPermissions {
+
+ private static final Logger logger = Logger.getLogger(ClientPermissionsV2.class);
+
+ public ClientPermissionsV2(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissionsV2 root) {
+ super(session, realm, authz, root);
+ }
+
+ @Override
+ public boolean canConfigure(ClientModel client) {
+ if (canManage(client)) return true;
+
+ return hasPermission(client, AdminPermissionsSchema.CONFIGURE);
+ }
+
+ @Override
+ public boolean canManage(ClientModel client) {
+ if (root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS)) return true;
+
+ return hasPermission(client, AdminPermissionsSchema.MANAGE);
+ }
+
+ @Override
+ public boolean canManage() {
+ return super.canManage() || hasPermission(AdminPermissionsSchema.MANAGE);
+ }
+
+ @Override
+ public boolean canView(ClientModel client) {
+ if (canView() || canConfigure(client)) {
+ return true;
+ }
+
+ return hasPermission(client, AdminPermissionsSchema.VIEW);
+ }
+
+ @Override
+ public boolean canView() {
+ return canViewClientDefault() || hasPermission(AdminPermissionsSchema.VIEW);
+ }
+
+ @Override
+ public boolean canMapRoles(ClientModel client) {
+ return hasPermission(client, AdminPermissionsSchema.MAP_ROLES);
+ }
+
+ @Override
+ public boolean canMapCompositeRoles(ClientModel client) {
+ return hasPermission(client, AdminPermissionsSchema.MAP_ROLES_COMPOSITE);
+ }
+
+ @Override
+ public boolean canMapClientScopeRoles(ClientModel client) {
+ return hasPermission(client, AdminPermissionsSchema.MAP_ROLES_CLIENT_SCOPE);
+ }
+
+ @Override
+ public boolean canManageClientScopes() {
+ if (root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS)) return true;
+
+ return hasPermission(AdminPermissionsSchema.MANAGE);
+ }
+
+ @Override
+ public boolean canManage(ClientScopeModel clientScope) {
+ return canManageClientScopes();
+ }
+
+ @Override
+ public boolean canView(ClientScopeModel clientScope) {
+ if (root.hasOneAdminRole(AdminRoles.VIEW_CLIENTS, AdminRoles.MANAGE_CLIENTS)) return true;
+
+ return hasPermission(AdminPermissionsSchema.VIEW) || hasPermission(AdminPermissionsSchema.MANAGE);
+ }
+
+ @Override
+ public Set getClientsWithPermission(String scope) {
+ if (!root.isAdminSameRealm()) {
+ return Collections.emptySet();
+ }
+
+ ResourceServer server = root.realmResourceServer();
+
+ if (server == null) {
+ return Collections.emptySet();
+ }
+
+ Set granted = new HashSet<>();
+
+ resourceStore.findByType(server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE, resource -> {
+ if (hasGrantedPermission(resource, scope)) {
+ granted.add(resource.getName());
+ }
+ });
+
+ return granted;
+ }
+
+ @Override
+ public boolean canExchangeTo(ClientModel authorizedClient, ClientModel to, AccessToken token) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy exchangeToPermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy mapRolesPermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy mapRolesClientScopePermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy mapRolesCompositePermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy managePermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy configurePermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Policy viewPermission(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public boolean isPermissionsEnabled(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public void setPermissionsEnabled(ClientModel client, boolean enable) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Resource resource(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ @Override
+ public Map getPermissions(ClientModel client) {
+ throw new UnsupportedOperationException("Not supported in V2");
+ }
+
+ private boolean hasPermission(ClientModel client, String scope) {
+ if (!root.isAdminSameRealm()) {
+ return false;
+ }
+ ResourceServer server = root.realmResourceServer();
+ if (server == null) return false;
+
+ Resource resource = resourceStore.findByName(server, client.getId(), server.getId());
+ if (resource == null) {
+ // check if there is permission for "all-clients". If so, load its resource and proceed with evaluation
+ resource = AdminPermissionsSchema.SCHEMA.getResourceTypeResource(session, server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE);
+
+ if (authz.getStoreFactory().getPolicyStore().findByResource(server, resource).isEmpty()) {
+ return false;
+ }
+ }
+
+ Collection permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
+ List expectedScopes = Arrays.asList(scope);
+ for (Permission permission : permissions) {
+ for (String s : permission.getScopes()) {
+ if (expectedScopes.contains(s)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean hasPermission(String scope) {
+ if (!root.isAdminSameRealm()) {
+ return false;
+ }
+ ResourceServer server = root.realmResourceServer();
+ if (server == null) return false;
+
+ Resource resource = resourceStore.findByName(server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE, server.getId());
+ if (resource == null) {
+ return false;
+ }
+
+ Collection permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
+ List expectedScopes = Arrays.asList(scope);
+ for (Permission permission : permissions) {
+ for (String s : permission.getScopes()) {
+ if (expectedScopes.contains(s)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private EvaluationContext getEvaluationContext(ClientModel authorizedClient, AccessToken token) {
+ ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient, token);
+ return new DefaultEvaluationContext(identity, session) {
+ @Override
+ public Map> getBaseAttributes() {
+ Map> attributes = super.getBaseAttributes();
+ attributes.put("kc.client.id", List.of(authorizedClient.getClientId()));
+ return attributes;
+ }
+ };
+ }
+
+ private boolean hasGrantedPermission(Resource resource, String scope) {
+ ResourceServer server = root.realmResourceServer();
+ Collection permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
+ for (Permission permission : permissions) {
+ for (String s : permission.getScopes()) {
+ if (scope.equals(s)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
index 75015ab7d92..70e2e7676e7 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
@@ -26,6 +26,8 @@ class MgmtPermissionsV2 extends MgmtPermissions {
private UserPermissionsV2 userPermissions;
+ private ClientPermissionsV2 clientPermissions;
+
public MgmtPermissionsV2(KeycloakSession session, RealmModel realm) {
super(session, realm);
}
@@ -57,4 +59,11 @@ public UserPermissions users() {
userPermissions = new UserPermissionsV2(session, authz, this);
return userPermissions;
}
+
+ @Override
+ public ClientPermissions clients() {
+ if (clientPermissions != null) return clientPermissions;
+ clientPermissions = new ClientPermissionsV2(session, realm, authz, this);
+ return clientPermissions;
+ }
}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java
index 63ffd4b48a2..b693d11749b 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java
@@ -17,18 +17,27 @@
package org.keycloak.tests.admin.authz.fgap;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import jakarta.ws.rs.core.Response;
+
+import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
+
import org.keycloak.admin.client.resource.PermissionsResource;
import org.keycloak.admin.client.resource.PoliciesResource;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.realm.ManagedClient;
@@ -42,7 +51,7 @@ public abstract class AbstractPermissionTest {
@InjectClient(attachTo = Constants.ADMIN_PERMISSIONS_CLIENT_ID)
ManagedClient client;
- protected PermissionsResource getPermissionsResource() {
+ protected static PermissionsResource getPermissionsResource(ManagedClient client) {
return client.admin().authorization().permissions();
}
@@ -50,16 +59,16 @@ protected PoliciesResource getPolicies() {
return client.admin().authorization().policies();
}
- protected ScopePermissionsResource getScopePermissionsResource() {
- return getPermissionsResource().scope();
+ protected static ScopePermissionsResource getScopePermissionsResource(ManagedClient client) {
+ return getPermissionsResource(client).scope();
}
- protected void createPermission(ScopePermissionRepresentation permission) {
- this.createPermission(permission, Response.Status.CREATED);
+ protected static void createPermission(ManagedClient client, ScopePermissionRepresentation permission) {
+ createPermission(client, permission, Response.Status.CREATED);
}
- protected void createPermission(ScopePermissionRepresentation permission, Response.Status expected) {
- try (Response response = getScopePermissionsResource().create(permission)) {
+ protected static void createPermission(ManagedClient client, ScopePermissionRepresentation permission, Response.Status expected) {
+ try (Response response = getScopePermissionsResource(client).create(permission)) {
assertEquals(expected.getStatusCode(), response.getStatus());
}
}
@@ -104,4 +113,68 @@ PermissionBuilder addPolicies(List policies) {
return this;
}
}
+
+ protected static UserPolicyRepresentation createUserPolicy(ManagedRealm realm, ManagedClient client, String name, String userId) {
+ return createUserPolicy(realm, client, name, userId, Logic.POSITIVE);
+ }
+
+ protected static UserPolicyRepresentation createUserPolicy(ManagedRealm realm, ManagedClient client, String name, String userId, Logic logic) {
+ UserPolicyRepresentation policy = new UserPolicyRepresentation();
+ policy.setName(name);
+ policy.addUser(userId);
+ policy.setLogic(logic);
+ try (Response response = client.admin().authorization().policies().user().create(policy)) {
+ assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
+ realm.cleanup().add(r -> {
+ String policyId = r.clients().get(client.getId()).authorization().policies().user().findByName(name).getId();
+ r.clients().get(client.getId()).authorization().policies().user().findById(policyId).remove();
+ });
+ }
+ return policy;
+ }
+
+ protected static ClientPolicyRepresentation createClientPolicy(ManagedRealm realm, ManagedClient client, String name, String clientId) {
+ ClientPolicyRepresentation policy = new ClientPolicyRepresentation();
+ policy.setName(name);
+ policy.addClient(clientId);
+ policy.setLogic(Logic.POSITIVE);
+ try (Response response = client.admin().authorization().policies().client().create(policy)) {
+ assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
+ realm.cleanup().add(r -> {
+ String policyId = r.clients().get(client.getId()).authorization().policies().client().findByName(name).getId();
+ r.clients().get(client.getId()).authorization().policies().client().findById(policyId).remove();
+ });
+ }
+ return policy;
+ }
+
+ protected static ScopePermissionRepresentation createAllPermission(ManagedClient client, String resourceType, AbstractPolicyRepresentation policy, Set scopes) {
+ ScopePermissionRepresentation permission = PermissionBuilder.create()
+ .resourceType(resourceType)
+ .scopes(scopes)
+ .addPolicies(List.of(policy.getName()))
+ .build();
+
+ createPermission(client, permission);
+
+ return permission;
+ }
+
+ protected ScopePermissionRepresentation createPermission(ManagedClient client, String resourceId, String resourceType, Set scopes, AbstractPolicyRepresentation... policies) {
+ return createPermission(client, Logic.POSITIVE, resourceId, resourceType, scopes, policies);
+ }
+
+ protected ScopePermissionRepresentation createPermission(ManagedClient client, Logic logic, String resourceId, String resourceType, Set scopes, AbstractPolicyRepresentation... policies) {
+ ScopePermissionRepresentation permission = PermissionBuilder.create()
+ .logic(logic)
+ .resourceType(resourceType)
+ .scopes(scopes)
+ .resources(Set.of(resourceId))
+ .addPolicies(Arrays.stream(policies).map(AbstractPolicyRepresentation::getName).collect(Collectors.toList()))
+ .build();
+
+ createPermission(client, permission);
+
+ return permission;
+ }
}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java
index 664220e0fd9..f6cfdc7b86d 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java
@@ -19,13 +19,35 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.fail;
+import static org.keycloak.authorization.AdminPermissionsSchema.CONFIGURE;
+import static org.keycloak.authorization.AdminPermissionsSchema.MANAGE;
+import static org.keycloak.authorization.AdminPermissionsSchema.MAP_ROLES;
+import static org.keycloak.authorization.AdminPermissionsSchema.MAP_ROLES_COMPOSITE;
+import static org.keycloak.authorization.AdminPermissionsSchema.VIEW;
+import java.util.List;
+import java.util.Set;
import java.util.function.Supplier;
+import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsSchema;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientScopeRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientScopePolicyRepresentation;
@@ -37,19 +59,37 @@
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.testframework.annotations.InjectAdminClient;
+import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
+import org.keycloak.testframework.realm.ManagedClient;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class PermissionClientTest extends AbstractPermissionTest {
+ @InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin")
+ Keycloak realmAdminClient;
+
+ @InjectClient(ref = "realmClient")
+ ManagedClient realmClient;
+
+ private final String clientsType = AdminPermissionsSchema.CLIENTS.getType();
+
+ @AfterEach
+ public void onAfter() {
+ ScopePermissionsResource permissions = getScopePermissionsResource(client);
+
+ permissions.findAll(null, null, null, -1, -1).forEach(p -> permissions.findById(p.getId()).remove());
+ }
+
@Test
public void testUnsupportedPolicyTypes() {
- assertSupportForPolicyType("resource", () -> getPermissionsResource().resource().create(new ResourcePermissionRepresentation()), false);
+ assertSupportForPolicyType("resource", () -> getPermissionsResource(client).resource().create(new ResourcePermissionRepresentation()), false);
}
@Test
public void testSupportedPolicyTypes() {
- assertSupportForPolicyType("scope", () -> getPermissionsResource().scope().create(PermissionBuilder.create()
+ assertSupportForPolicyType("scope", () -> getPermissionsResource(client).scope().create(PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.scopes(AdminPermissionsSchema.USERS.getScopes())
.build()), true);
@@ -82,4 +122,245 @@ private void assertPolicyEndpointResponse(String type, boolean supported, Respon
assertThat("Policy type [" + type + "] should be " + (supported ? "supported" : "unsupported"), Status.BAD_REQUEST.equals(Status.fromStatusCode(response.getStatus())), not(supported));
assertThat("Policy type [" + type + "] should be " + (supported ? "supported" : "unsupported"), response.readEntity(String.class).contains("Policy type not supported by feature"), not(supported));
}
+
+ @Test
+ public void testManageOnlyOneClient() {
+ ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
+ UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
+
+ ClientResource clientResource = realmAdminClient.realm(realm.getName()).clients().get(myclient.getId());
+
+ // the following operations should fail as the permission wasn't granted yet
+ try {
+ clientResource.toRepresentation();
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ try {
+ myclient.setName("somethingNew");
+ clientResource.update(myclient);
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ try {
+ ClientScopeRepresentation clientScopeRep = clientResource.getDefaultClientScopes().get(1);
+ clientResource.removeDefaultClientScope(clientScopeRep.getId());
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+
+ UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
+ createPermission(client, myclient.getId(), clientsType, Set.of(MANAGE), onlyMyAdminUserPolicy);
+
+ // the caller can view myclient
+ clientResource.toRepresentation();
+
+ // the caller can list myclient
+ List allClients = realmAdminClient.realm(realm.getName()).clients().findAll();
+ assertThat(allClients, hasSize(1));
+
+ // can update myclient
+ myclient.setName("somethingNew");
+ clientResource.update(myclient);
+
+ // can view client scopes
+ List defaultClientScopes = clientResource.getDefaultClientScopes();
+ assertThat(defaultClientScopes, not(empty()));
+
+ // can remove a default client scope
+ ClientScopeRepresentation clientScopeRep = defaultClientScopes.get(1);
+ clientResource.removeDefaultClientScope(clientScopeRep.getId());
+
+ // can add an optional client scope
+ clientResource.addOptionalClientScope(clientScopeRep.getId());
+
+ // can't update a different client
+ ClientRepresentation realmClientRep = realm.admin().clients().get(realmClient.getId()).toRepresentation();
+ realmClientRep.setName("somethingNew");
+ try {
+ realmAdminClient.realm(realm.getName()).clients().get(realmClient.getId()).update(realmClientRep);
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ }
+
+ @Test
+ public void testConfigureOnlyOneClient() {
+ ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
+ UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
+
+ ClientResource clientResource = realmAdminClient.realm(realm.getName()).clients().get(myclient.getId());
+
+ // the following operations should fail as the permission wasn't granted yet
+ try {
+ clientResource.toRepresentation();
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ try {
+ clientResource.generateNewSecret();
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ try {
+ clientResource.getDefaultClientScopes();
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+
+ UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
+ createPermission(client, myclient.getId(), clientsType, Set.of(CONFIGURE), onlyMyAdminUserPolicy);
+
+ // the caller can view myclient
+ clientResource.toRepresentation();
+
+ // can do operations with client secrets
+ clientResource.getSecret();
+ clientResource.generateNewSecret();
+ clientResource.invalidateRotatedSecret();
+
+ // can get default client scopes
+ List defaultClientScopes = clientResource.getDefaultClientScopes();
+
+ // can't delete default client scopes
+ try {
+ clientResource.removeDefaultClientScope(defaultClientScopes.get(0).getId());
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+
+ // can't add default client scopes
+ try {
+ clientResource.addDefaultClientScope(defaultClientScopes.get(0).getId());
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ }
+
+ @Test
+ public void testManageAllClients() {
+ UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
+
+ ClientRepresentation newClient = new ClientRepresentation();
+ newClient.setClientId("newClient");
+ newClient.setProtocol("openid-connect");
+
+ // the following operations should fail as the permission wasn't granted yet
+ try (Response response = realmAdminClient.realm(realm.getName()).clients().create(newClient)) {
+ Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ }
+
+ List found = realmAdminClient.realm(realm.getName()).clients().findAll();
+ assertThat(found, empty());
+
+ UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
+ createAllPermission(client, clientsType, onlyMyAdminUserPolicy, Set.of(MANAGE));
+
+ // can create a new client
+ realmAdminClient.realm(realm.getName()).clients().create(newClient).close();
+ found = realmAdminClient.realm(realm.getName()).clients().findByClientId("newClient");
+ assertThat(found, hasSize(1));
+
+ // can delete
+ realmAdminClient.realm(realm.getName()).clients().get(found.get(0).getId()).remove();
+ found = realmAdminClient.realm(realm.getName()).clients().findByClientId("newClient");
+ assertThat(found, empty());
+
+ // can list & view all clients
+ found = realmAdminClient.realm(realm.getName()).clients().findAll();
+ assertThat(found, not(empty()));
+ }
+
+ @Test
+ public void testViewAllClients() {
+ ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
+ UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
+
+ // the following operations should fail as the permission wasn't granted yet
+ try {
+ realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).getProtocolMappers().getMappers();
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+
+ List found = realmAdminClient.realm(realm.getName()).clients().findAll(true);
+ assertThat(found, empty());
+
+
+ UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
+ createAllPermission(client, clientsType, onlyMyAdminUserPolicy, Set.of(VIEW));
+
+ // can list and view all clients
+ found = realmAdminClient.realm(realm.getName()).clients().findAll(true);
+ assertThat(found, not(empty()));
+
+ // can't create a protocol mapper
+ ProtocolMapperRepresentation protocolMapperRep = new ProtocolMapperRepresentation();
+ protocolMapperRep.setName("my-protocol-mapper");
+ protocolMapperRep.setProtocol("openid-connect");
+ protocolMapperRep.setProtocolMapper("oidc-hardcoded-claim-mapper");
+ try (Response response = realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).getProtocolMappers().createMapper(protocolMapperRep)) {
+ Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ }
+
+ // can get protocol mappers
+ assertThat(realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).getProtocolMappers().getMappers(), empty());
+
+ // can't create a new client
+ try (Response response = realmAdminClient.realm(realm.getName()).clients().create(null)) {
+ Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ }
+ }
+
+ @Test
+ public void testMapRolesAndCompositesOnlyOneClient() {
+ UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
+ ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
+
+ // create a role and sub-role
+ RoleRepresentation role = new RoleRepresentation();
+ role.setName("myclient-role");
+ role.setClientRole(true);
+ realm.admin().clients().get(myclient.getId()).roles().create(role);
+ role = realm.admin().clients().get(myclient.getId()).roles().get("myclient-role").toRepresentation();
+
+ RoleRepresentation subRole = new RoleRepresentation();
+ subRole.setName("myclient-subRole");
+ subRole.setClientRole(true);
+ realm.admin().clients().get(myclient.getId()).roles().create(subRole);
+ subRole = realm.admin().clients().get(myclient.getId()).roles().get("myclient-subRole").toRepresentation();
+
+ // the following operations should fail as the permission wasn't granted yet
+ try {
+ realmAdminClient.realm(realm.getName()).users().get(myadmin.getId()).roles().clientLevel(myclient.getId()).add(List.of(role));
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+ try {
+ realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).roles().get("myclient-role").addComposites(List.of(subRole));
+ fail("Expected exception wasn't thrown.");
+ } catch (Exception ex) {
+ assertThat(ex, instanceOf(ForbiddenException.class));
+ }
+
+ UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
+ createPermission(client, myadmin.getId(), AdminPermissionsSchema.USERS_RESOURCE_TYPE, Set.of(MAP_ROLES), onlyMyAdminUserPolicy);
+ createPermission(client, myclient.getId(), clientsType, Set.of(MAP_ROLES, MAP_ROLES_COMPOSITE, CONFIGURE), onlyMyAdminUserPolicy);
+
+ // now those should pass
+ realmAdminClient.realm(realm.getName()).users().get(myadmin.getId()).roles().clientLevel(myclient.getId()).add(List.of(role));
+
+ realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).roles().get("myclient-role").addComposites(List.of(subRole));
+ }
}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java
index fd221b76f17..74dd8cf7c1c 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java
@@ -126,7 +126,7 @@ public void resourcesTest() {
}
// this should create a resource for userAlice
- createPermission(PermissionBuilder.create()
+ createPermission(client, PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.resources(Set.of(userAlice.getUsername()))
.scopes(AdminPermissionsSchema.USERS.getScopes())
@@ -157,17 +157,17 @@ public void resourcesTest() {
@Test
public void permissionsTest() {
// no resourceType, valid scopes
- createPermission(PermissionBuilder.create()
+ createPermission(client, PermissionBuilder.create()
.scopes(AdminPermissionsSchema.USERS.getScopes())
.build(), Response.Status.BAD_REQUEST);
// valid resourceType, no scopes
- createPermission(PermissionBuilder.create()
+ createPermission(client, PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.build(), Response.Status.BAD_REQUEST);
// valid resourceType, non-existent scopes
- createPermission(PermissionBuilder.create()
+ createPermission(client, PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.scopes(Set.of("edit", "write", "token-exchange"))
.build(), Response.Status.BAD_REQUEST);
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
index 3379e416522..13996fc34dd 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
@@ -18,7 +18,6 @@
package org.keycloak.tests.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -36,7 +35,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -46,7 +44,6 @@
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
@@ -66,11 +63,13 @@ public class UserResourceTypeEvaluationTest extends AbstractPermissionTest {
@InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin")
Keycloak realmAdminClient;
+ private final String usersType = AdminPermissionsSchema.USERS.getType();
+
private final String newUserUsername = "new_user";
@AfterEach
public void onAfter() {
- ScopePermissionsResource permissions = getScopePermissionsResource();
+ ScopePermissionsResource permissions = getScopePermissionsResource(client);
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
permissions.findById(permission.getId()).remove();
@@ -82,9 +81,9 @@ public void onAfter() {
@Test
public void testSingleUserPermission() {
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
- UserPolicyRepresentation allowMyAdminPermission = createUserPolicy("Only My Admin User Policy", myadmin.getId());
+ UserPolicyRepresentation allowMyAdminPermission = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
// allow my admin to see alice only
- createUserPermission(userAlice.admin().toRepresentation(), Set.of(VIEW), allowMyAdminPermission);
+ createPermission(client, userAlice.admin().toRepresentation().getId(), usersType, Set.of(VIEW), allowMyAdminPermission);
List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
assertEquals(1, search.size());
assertEquals(userAlice.getUsername(), search.get(0).getUsername());
@@ -101,11 +100,11 @@ public void testImpersonatePermission() {
}
//create all-users permission for "myadmin" (so that myadmin can impersonate all users in the realm)
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- createAllUserPermission(policy, Set.of(IMPERSONATE));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ createAllPermission(client, usersType, policy, Set.of(IMPERSONATE));
// create user permission forbidding the impersonation for userAlice
- String cannotImpersonateAlice = createUserPermission(Logic.NEGATIVE, userAlice.admin().toRepresentation(), Set.of(IMPERSONATE)).getName();
+ String cannotImpersonateAlice = createPermission(client, Logic.NEGATIVE, userAlice.getId(), usersType, Set.of(IMPERSONATE)).getName();
// even though "myadmin" has permission to impersonate all users in realm it should be denied to impersonate userAlice
try {
@@ -116,8 +115,8 @@ public void testImpersonatePermission() {
}
// remove the negative permission
- String cannotImpersonateAliceId = getScopePermissionsResource().findByName(cannotImpersonateAlice).getId();
- getScopePermissionsResource().findById(cannotImpersonateAliceId).remove();
+ String cannotImpersonateAliceId = getScopePermissionsResource(client).findByName(cannotImpersonateAlice).getId();
+ getScopePermissionsResource(client).findById(cannotImpersonateAliceId).remove();
// need to create a separate client for the impersonation call, otherwise next usage of the 'realmAdminClient' would throw 401
try (Keycloak adminClient = KeycloakBuilder.builder()
@@ -133,25 +132,6 @@ public void testImpersonatePermission() {
}
}
- private UserPolicyRepresentation createUserPolicy(String name, String userId) {
- return createUserPolicy(name, userId, Logic.POSITIVE);
- }
-
- private UserPolicyRepresentation createUserPolicy(String name, String userId, Logic logic) {
- UserPolicyRepresentation policy = new UserPolicyRepresentation();
- policy.setName(name);
- policy.addUser(userId);
- policy.setLogic(logic);
- try (Response response = client.admin().authorization().policies().user().create(policy)) {
- assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
- realm.cleanup().add(r -> {
- String policyId = r.clients().get(client.getId()).authorization().policies().user().findByName(name).getId();
- r.clients().get(client.getId()).authorization().policies().user().findById(policyId).remove();
- });
- }
- return policy;
- }
-
@Test
public void testManageAllPermission() {
// myadmin shouldn't be able to create user just yet
@@ -160,8 +140,8 @@ public void testManageAllPermission() {
}
//create all-users permission for "myadmin" (so that myadmin can manage all users in the realm)
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- createAllUserPermission(policy, Set.of(MANAGE));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ createAllPermission(client, usersType, policy, Set.of(MANAGE));
// creating user requires manage scope
String newUserId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).users().create(UserConfigBuilder.create().username(newUserUsername).build()));
@@ -174,26 +154,26 @@ public void testManageAllPermission() {
@Test
public void testManageUserPermission() {
String myadminId = realm.admin().users().search("myadmin").get(0).getId();
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", myadminId);
- ScopePermissionRepresentation allUsersPermission = createAllUserPermission(policy, Set.of(MANAGE));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", myadminId);
+ ScopePermissionRepresentation allUsersPermission = createAllPermission(client, usersType, policy, Set.of(MANAGE));
// creating user requires manage scope
String newUserId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).users().create(UserConfigBuilder.create().username(newUserUsername).build()));
// remove all-users permissions to test user-permission
- allUsersPermission = getScopePermissionsResource().findByName(allUsersPermission.getName());
- getScopePermissionsResource().findById(allUsersPermission.getId()).remove();
+ allUsersPermission = getScopePermissionsResource(client).findByName(allUsersPermission.getName());
+ getScopePermissionsResource(client).findById(allUsersPermission.getId()).remove();
// create user-permissions
- createUserPermission(UserConfigBuilder.create().id(newUserId).build(), Set.of(MANAGE), policy);
+ createPermission(client, UserConfigBuilder.create().id(newUserId).build().getId(), usersType, Set.of(MANAGE), policy);
// it should be possible to update the user due to single user-permission
realmAdminClient.realm(realm.getName()).users().get(newUserId).update(UserConfigBuilder.create().email("email@test.com").build());
assertEquals("email@test.com", realmAdminClient.realm(realm.getName()).users().get(newUserId).toRepresentation().getEmail());
// remove the user permission
- getScopePermissionsResource().findAll(null, null, null, null, null).forEach(permission -> {
- getScopePermissionsResource().findById(permission.getId()).remove();
+ getScopePermissionsResource(client).findAll(null, null, null, null, null).forEach(permission -> {
+ getScopePermissionsResource(client).findById(permission.getId()).remove();
});
// updating the user should be denied
@@ -207,8 +187,8 @@ public void testManageUserPermission() {
@Test
public void testViewAllPermission() {
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- ScopePermissionRepresentation permission = createAllUserPermission(policy, Set.of(VIEW));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ ScopePermissionRepresentation permission = createAllPermission(client, usersType, policy, Set.of(VIEW));
List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
assertFalse(search.isEmpty());
@@ -222,11 +202,11 @@ public void testViewAllPermission() {
@Test
public void testViewUserPermission() {
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
- UserPolicyRepresentation allowMyAdminPermission = createUserPolicy("Only My Admin User Policy", myadmin.getId());
- createAllUserPermission(allowMyAdminPermission, Set.of(VIEW));
+ UserPolicyRepresentation allowMyAdminPermission = createUserPolicy(realm, client,"Only My Admin User Policy", myadmin.getId());
+ createAllPermission(client, usersType, allowMyAdminPermission, Set.of(VIEW));
- UserPolicyRepresentation denyMyAdminAccessingHisAccountPermission = createUserPolicy("Not My Admin User Policy", myadmin.getId(), Logic.NEGATIVE);
- createUserPermission(myadmin, Set.of(VIEW), denyMyAdminAccessingHisAccountPermission);
+ UserPolicyRepresentation denyMyAdminAccessingHisAccountPermission = createUserPolicy(realm, client,"Not My Admin User Policy", myadmin.getId(), Logic.NEGATIVE);
+ createPermission(client, myadmin.getId(), usersType, Set.of(VIEW), denyMyAdminAccessingHisAccountPermission);
List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
assertEquals(1, search.size());
assertEquals(userAlice.getUsername(), search.get(0).getUsername());
@@ -235,11 +215,11 @@ public void testViewUserPermission() {
@Test
public void testViewUserPermissionDenyByDefault() {
String myadminId = realm.admin().users().search("myadmin").get(0).getId();
- UserPolicyRepresentation disallowMyAdmin = createUserPolicy("Not My Admin User Policy", myadminId, Logic.NEGATIVE);
- createAllUserPermission(disallowMyAdmin, Set.of(VIEW));
+ UserPolicyRepresentation disallowMyAdmin = createUserPolicy(realm, client,"Not My Admin User Policy", myadminId, Logic.NEGATIVE);
+ createAllPermission(client, usersType, disallowMyAdmin, Set.of(VIEW));
- UserPolicyRepresentation allowAliceOnlyForMyAdmin = createUserPolicy("My Admin User Policy", myadminId);
- createUserPermission(userAlice.admin().toRepresentation(), Set.of(VIEW), allowAliceOnlyForMyAdmin);
+ UserPolicyRepresentation allowAliceOnlyForMyAdmin = createUserPolicy(realm, client,"My Admin User Policy", myadminId);
+ createPermission(client, userAlice.getId(), usersType, Set.of(VIEW), allowAliceOnlyForMyAdmin);
List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
assertEquals(1, search.size());
@@ -262,8 +242,8 @@ public void testMapRoles() {
}
//create all-users permission for "myadmin" (so that myadmin can map roles to users in the realm)
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- ScopePermissionRepresentation allUsersPermission = createAllUserPermission(policy, Set.of(MAP_ROLES));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ ScopePermissionRepresentation allUsersPermission = createAllPermission(client, usersType, policy, Set.of(MAP_ROLES));
try {
realmAdminClient.realm(realm.getName()).users().get(userAlice.getId()).roles().realmLevel().add(List.of(testRole));
@@ -277,8 +257,8 @@ public void testMapRoles() {
}
// remove all-users permissions to test user-permission
- allUsersPermission = getScopePermissionsResource().findByName(allUsersPermission.getName());
- getScopePermissionsResource().findById(allUsersPermission.getId()).remove();
+ allUsersPermission = getScopePermissionsResource(client).findByName(allUsersPermission.getName());
+ getScopePermissionsResource(client).findById(allUsersPermission.getId()).remove();
// now myadmin cannot map roles
try {
@@ -289,7 +269,7 @@ public void testMapRoles() {
}
// create userPermission
- createUserPermission(userAlice.admin().toRepresentation(), Set.of(MAP_ROLES), policy);
+ createPermission(client, userAlice.getId(), usersType, Set.of(MAP_ROLES), policy);
//check myadmin can map roles
try {
@@ -312,8 +292,8 @@ public void testManageGroupMembership() {
}
//create all-users permission for "myadmin" (so that myadmin can add users into a group)
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- ScopePermissionRepresentation allUsersPermission = createAllUserPermission(policy, Set.of(MANAGE_GROUP_MEMBERSHIP));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ ScopePermissionRepresentation allUsersPermission = createAllPermission(client, usersType, policy, Set.of(MANAGE_GROUP_MEMBERSHIP));
//check myadmin can manage membership using all-users permission
try {
@@ -325,8 +305,8 @@ public void testManageGroupMembership() {
}
// remove all-users permissions to test user-permission
- allUsersPermission = getScopePermissionsResource().findByName(allUsersPermission.getName());
- getScopePermissionsResource().findById(allUsersPermission.getId()).remove();
+ allUsersPermission = getScopePermissionsResource(client).findByName(allUsersPermission.getName());
+ getScopePermissionsResource(client).findById(allUsersPermission.getId()).remove();
// now myadmin cannot manage membership
try {
@@ -337,7 +317,7 @@ public void testManageGroupMembership() {
}
// create userPermission
- createUserPermission(userAlice.admin().toRepresentation(), Set.of(MANAGE_GROUP_MEMBERSHIP), policy);
+ createPermission(client, userAlice.getId(), usersType, Set.of(MANAGE_GROUP_MEMBERSHIP), policy);
//check myadmin can manage membership using individual permission
try {
@@ -352,8 +332,8 @@ public void testManageGroupMembership() {
@Test
public void testTransitiveUserPermissions() {
//create all-users manage permission for "myadmin"
- UserPolicyRepresentation policy = createUserPolicy("Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
- createAllUserPermission(policy, Set.of(MANAGE));
+ UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
+ createAllPermission(client, usersType, policy, Set.of(MANAGE));
// with manage permission it is possible also view
List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
@@ -385,34 +365,4 @@ public void testTransitiveUserPermissions() {
assertThat(ex, instanceOf(ForbiddenException.class));
}
}
-
- private ScopePermissionRepresentation createAllUserPermission(UserPolicyRepresentation policy, Set scopes) {
- ScopePermissionRepresentation permission = PermissionBuilder.create()
- .resourceType(AdminPermissionsSchema.USERS.getType())
- .scopes(scopes)
- .addPolicies(List.of(policy.getName()))
- .build();
-
- createPermission(permission);
-
- return permission;
- }
-
- private ScopePermissionRepresentation createUserPermission(UserRepresentation user, Set scopes, UserPolicyRepresentation... policies) {
- return createUserPermission(Logic.POSITIVE, user, scopes, policies);
- }
-
- private ScopePermissionRepresentation createUserPermission(Logic logic, UserRepresentation user, Set scopes, UserPolicyRepresentation... policies) {
- ScopePermissionRepresentation permission = PermissionBuilder.create()
- .logic(logic)
- .resourceType(AdminPermissionsSchema.USERS.getType())
- .scopes(scopes)
- .resources(Set.of(user.getId()))
- .addPolicies(Arrays.asList(policies).stream().map(AbstractPolicyRepresentation::getName).collect(Collectors.toList()))
- .build();
-
- createPermission(permission);
-
- return permission;
- }
}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java
index 947196cc37f..acdd9c39892 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java
@@ -64,7 +64,7 @@ public void onBefore() {
@AfterEach
public void onAfter() {
- ScopePermissionsResource permissions = getScopePermissionsResource();
+ ScopePermissionsResource permissions = getScopePermissionsResource(client);
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
permissions.findById(permission.getId()).remove();
@@ -74,10 +74,10 @@ public void onAfter() {
@Test
public void testCreateResourceTypePermission() {
ScopePermissionRepresentation expected = createAllUserPermission();
- List result = getScopePermissionsResource().findAll(null, null, null, -1, -1);
+ List result = getScopePermissionsResource(client).findAll(null, null, null, -1, -1);
assertEquals(1, result.size());
ScopePermissionRepresentation permissionRep = result.get(0);
- ScopePermissionResource permission = getScopePermissionsResource().findById(permissionRep.getId());
+ ScopePermissionResource permission = getScopePermissionsResource(client).findById(permissionRep.getId());
assertEquals(expected.getName(), permissionRep.getName());
assertEquals(AdminPermissionsSchema.USERS.getScopes().size(), permission.scopes().size());
assertEquals(3, permission.associatedPolicies().size());
@@ -86,10 +86,10 @@ public void testCreateResourceTypePermission() {
@Test
public void testCreateResourceObjectPermission() {
ScopePermissionRepresentation expected = createUserPermission(userAlice);
- List result = getScopePermissionsResource().findAll(null, null, null, -1, -1);
+ List result = getScopePermissionsResource(client).findAll(null, null, null, -1, -1);
assertEquals(1, result.size());
ScopePermissionRepresentation permissionRep = result.get(0);
- ScopePermissionResource permission = getScopePermissionsResource().findById(permissionRep.getId());
+ ScopePermissionResource permission = getScopePermissionsResource(client).findById(permissionRep.getId());
assertEquals(expected.getName(), permissionRep.getName());
assertEquals(AdminPermissionsSchema.USERS.getScopes().size(), permission.scopes().size());
assertEquals(1, permission.resources().size());
@@ -100,9 +100,9 @@ public void testCreateResourceObjectPermission() {
public void testFindByResourceObject() {
createUserPermission(userAlice, userBob);
- List existing = getScopePermissionsResource().findAll(null, null, userAlice.getId(), -1, -1);
+ List existing = getScopePermissionsResource(client).findAll(null, null, userAlice.getId(), -1, -1);
assertEquals(1, existing.size());
- existing = getScopePermissionsResource().findAll(null, null, userBob.getId(), -1, -1);
+ existing = getScopePermissionsResource(client).findAll(null, null, userBob.getId(), -1, -1);
assertEquals(1, existing.size());
}
@@ -122,18 +122,18 @@ public void testDelete() {
resources = client.admin().authorization().resources().find(null, null, null, null, null, null, null);
assertEquals(2 + AdminPermissionsSchema.SCHEMA.getResourceTypes().entrySet().size(), resources.size());
- List existing = getScopePermissionsResource().findAll(null, null, userAlice.getId(), -1, -1);
+ List existing = getScopePermissionsResource(client).findAll(null, null, userAlice.getId(), -1, -1);
assertEquals(1, existing.size());
// remove permission for Alice
- getScopePermissionsResource().findById(existing.get(0).getId()).remove();
- existing = getScopePermissionsResource().findAll(null, null, userAlice.getId(), -1, -1);
+ getScopePermissionsResource(client).findById(existing.get(0).getId()).remove();
+ existing = getScopePermissionsResource(client).findAll(null, null, userAlice.getId(), -1, -1);
assertThat(existing, nullValue());
- existing = getScopePermissionsResource().findAll(null, null, userBob.getId(), -1, -1);
+ existing = getScopePermissionsResource(client).findAll(null, null, userBob.getId(), -1, -1);
assertEquals(1, existing.size());
// remove permission for Bob
- getScopePermissionsResource().findById(existing.get(0).getId()).remove();
+ getScopePermissionsResource(client).findById(existing.get(0).getId()).remove();
//resources for both Alice and Bob should be deleted, there should be only "all-resource" resources
resources = client.admin().authorization().resources().find(null, null, null, null, null, null, null);
@@ -144,9 +144,9 @@ public void testDelete() {
public void testUpdate() {
createUserPermission(userAlice, userBob);
- List searchByResourceAlice = getScopePermissionsResource().findAll(null, null, userAlice.getId(), -1, -1);
+ List searchByResourceAlice = getScopePermissionsResource(client).findAll(null, null, userAlice.getId(), -1, -1);
assertThat(searchByResourceAlice, hasSize(1));
- List searchByResourceBob = getScopePermissionsResource().findAll(null, null, userBob.getId(), -1, -1);
+ List searchByResourceBob = getScopePermissionsResource(client).findAll(null, null, userBob.getId(), -1, -1);
assertThat(searchByResourceBob, hasSize(1));
ScopePermissionRepresentation permission = searchByResourceAlice.get(0);
@@ -162,7 +162,7 @@ public void testUpdate() {
ResourceRepresentation toRemove = resources.get(1);
resources.remove(toRemove);
permission.setResources(resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()));
- getScopePermissionsResource().findById(permission.getId()).update(permission);
+ getScopePermissionsResource(client).findById(permission.getId()).update(permission);
//permission should have only single resource
assertThat(getPolicies().policy(permission.getId()).resources(), hasSize(1));
@@ -183,7 +183,7 @@ private ScopePermissionRepresentation createUserPermission(ManagedUser... users)
.addPolicies(List.of("User Policy 0", "User Policy 1", "User Policy 2"))
.build();
- createPermission(permission);
+ createPermission(client, permission);
return permission;
}
@@ -195,7 +195,7 @@ private ScopePermissionRepresentation createAllUserPermission() {
.addPolicies(List.of("User Policy 0", "User Policy 1", "User Policy 2"))
.build();
- createPermission(permission);
+ createPermission(client, permission);
return permission;
}