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