From 28b3ef9aa909e1e6583dee87aeb8487c0fb38af4 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 26 Jun 2017 11:40:32 -0400 Subject: [PATCH] admin console work --- .../java/org/keycloak/RSAVerifierTest.java | 5 +- .../AuthenticationManagementResource.java | 4 +- .../ClientAttributeCertificateResource.java | 8 +- .../resources/admin/ClientResource.java | 38 +++++++-- .../ClientPermissionEvaluator.java | 4 + .../ClientPermissionManagement.java | 3 + .../admin/permissions/ClientPermissions.java | 78 +++++++++++++++++-- .../admin/permissions/GroupPermissions.java | 6 +- .../admin/permissions/RolePermissions.java | 2 +- .../admin/permissions/UserPermissions.java | 1 + .../admin/FineGrainAdminUnitTest.java | 70 +++++++++++++++-- .../messages/admin-messages_en.properties | 11 ++- .../resources/js/authz/authz-controller.js | 10 ++- .../authz/mgmt/client-permissions.html | 4 +- .../authz/mgmt/client-role-permissions.html | 4 +- .../partials/client-clustering-node.html | 6 +- .../resources/partials/client-clustering.html | 6 +- .../partials/client-credentials-generic.html | 4 +- .../client-credentials-jwt-key-export.html | 4 +- .../client-credentials-jwt-key-import.html | 2 +- .../partials/client-credentials-jwt.html | 4 +- .../partials/client-credentials-secret.html | 4 +- .../partials/client-credentials.html | 2 +- .../resources/partials/client-detail.html | 6 +- .../admin/resources/partials/client-keys.html | 4 +- .../client-registration-access-token.html | 4 +- .../resources/partials/client-revocation.html | 4 +- .../partials/client-role-detail.html | 6 +- .../resources/partials/client-role-list.html | 4 +- .../partials/client-saml-key-export.html | 4 +- .../partials/client-saml-key-import.html | 2 +- .../resources/partials/client-saml-keys.html | 6 +- .../partials/client-scope-mappings.html | 4 +- .../client-service-account-roles.html | 4 +- .../resources/templates/kc-tabs-client.html | 2 +- .../resources/templates/kc-tabs-group.html | 4 +- .../resources/templates/kc-tabs-role.html | 2 +- .../resources/templates/kc-tabs-users.html | 2 +- 38 files changed, 251 insertions(+), 87 deletions(-) diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java index 58e1f38d73..e606e35a94 100755 --- a/core/src/test/java/org/keycloak/RSAVerifierTest.java +++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java @@ -234,13 +234,16 @@ public class RSAVerifierTest { public void testTokenAuth() throws Exception { token = new AccessToken(); token.subject("CN=Client") - .issuer("domain") + .issuer("http://localhost:8080/auth/realms/demo") .addAccess("service").addRole("admin").verifyCaller(true); + token.setEmail("bill@jboss.org"); String encoded = new JWSBuilder() .jsonContent(token) .rsa256(idpPair.getPrivate()); + System.out.println("token size: " + encoded.length()); + AccessToken v = null; try { v = verifySkeletonKeyToken(encoded); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 88650c178a..61f6254710 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -137,7 +137,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getClientAuthenticatorProviders() { - auth.realm().requireViewRealm(); + auth.requireAnyAdminRole(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); return buildProviderMetadata(factories); @@ -959,7 +959,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public Map> getPerClientConfigDescription() { - auth.realm().requireViewRealm(); + auth.requireAnyAdminRole(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index e3dda51b98..a798414247 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -115,7 +115,7 @@ public class ClientAttributeCertificateResource { @Path("generate") @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation generate() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId()); @@ -139,7 +139,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -165,7 +165,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -306,7 +306,7 @@ public class ClientAttributeCertificateResource { @Produces(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_JSON) public byte[] generateAndGetKeystore(final KeyStoreConfig config) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { throw new NotAcceptableException("Only support jks or pkcs12 format."); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 77440be34c..e7d611eaeb 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -27,6 +27,7 @@ import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -41,6 +42,7 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.protocol.ClientInstallationProvider; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.UserRepresentation; @@ -134,7 +136,7 @@ public class ClientResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response update(final ClientRepresentation rep) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); ValidationMessages validationMessages = new ValidationMessages(); if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { @@ -227,7 +229,7 @@ public class ClientResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public CredentialRepresentation regenerateSecret() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); logger.debug("regenerateSecret"); UserCredentialModel cred = KeycloakModelUtils.generateSecret(client); @@ -326,7 +328,7 @@ public class ClientResource { @POST @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult pushRevocation() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).resource(ResourceType.CLIENT).success(); return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client); @@ -456,7 +458,7 @@ public class ClientResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void registerNode(Map formParams) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); String node = formParams.get("node"); if (node == null) { @@ -476,7 +478,7 @@ public class ClientResource { @DELETE @NoCache public void unregisterNode(final @PathParam("node") String node) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); if (logger.isDebugEnabled()) logger.debug("Unregister node: " + node); @@ -500,7 +502,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult testNodesAvailable() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); logger.debug("Test availability of cluster nodes"); GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client); @@ -583,6 +585,30 @@ public class ClientResource { new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId()); } + if (rep.isFullScopeAllowed() != null && rep.isFullScopeAllowed().booleanValue() != client.isFullScopeAllowed()) { + auth.clients().requireManage(client); + } + + if (rep.getClientTemplate() != null) { + ClientTemplateModel currTemplate = client.getClientTemplate(); + if (currTemplate == null) { + if (!rep.getClientTemplate().equals(ClientTemplateRepresentation.NONE)) { + auth.clients().requireManage(client); + } + } else if (!rep.getClientTemplate().equals(currTemplate.getName())){ + auth.clients().requireManage(client); + } + if ((rep.isUseTemplateConfig() != null && rep.isUseTemplateConfig().booleanValue() != client.useTemplateConfig()) + || (rep.isUseTemplateScope() != null && rep.isUseTemplateScope().booleanValue() != client.useTemplateScope()) + || (rep.isUseTemplateMappers() != null && rep.isUseTemplateMappers().booleanValue() != client.useTemplateMappers()) + + ) { + auth.clients().requireManage(client); + } + } + + + RepresentationToModel.updateClient(rep, client); } 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 25da468837..3b64a27e12 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 @@ -56,6 +56,10 @@ public interface ClientPermissionEvaluator { boolean canManage(ClientModel client); + boolean canConfigure(ClientModel client); + + void requireConfigure(ClientModel client); + void requireManage(ClientModel client); boolean canView(ClientModel client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java index d967443264..8a6b76dd8e 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java @@ -31,6 +31,7 @@ public interface ClientPermissionManagement { public static final String MAP_ROLES_SCOPE = "map-roles"; public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope"; public static final String MAP_ROLES_COMPOSITE_SCOPE = "map-roles-composite"; + public static final String CONFIGURE_SCOPE = "configure"; boolean isPermissionsEnabled(ClientModel client); @@ -48,6 +49,8 @@ public interface ClientPermissionManagement { Policy managePermission(ClientModel client); + Policy configurePermission(ClientModel client); + Policy viewPermission(ClientModel client); ResourceServer resourceServer(ClientModel client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java index 95e440eb6e..2b1e2340c4 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -63,6 +63,9 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private String getManagePermissionName(ClientModel client) { return "manage.permission.client." + client.getId(); } + private String getConfigurePermissionName(ClientModel client) { + return "configure.permission.client." + client.getId(); + } private String getViewPermissionName(ClientModel client) { return "view.permission.client." + client.getId(); } @@ -98,6 +101,10 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (mapRoleCompositeScope == null) { mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_COMPOSITE_SCOPE, server); } + Scope configureScope = authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId()); + if (configureScope == null) { + configureScope = authz.getStoreFactory().getScopeStore().create(CONFIGURE_SCOPE, server); + } String resourceName = getResourceName(client); Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId()); @@ -105,6 +112,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa resource = authz.getStoreFactory().getResourceStore().create(resourceName, server, server.getClientId()); resource.setType("Client"); Set scopeset = new HashSet<>(); + scopeset.add(configureScope); scopeset.add(manageScope); scopeset.add(viewScope); scopeset.add(mapRoleScope); @@ -117,6 +125,11 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (managePermission == null) { Helper.addEmptyScopePermission(authz, server, managePermissionName, resource, manageScope); } + String configurePermissionName = getConfigurePermissionName(client); + Policy configurePermission = authz.getStoreFactory().getPolicyStore().findByName(configurePermissionName, server.getId()); + if (configurePermission == null) { + Helper.addEmptyScopePermission(authz, server, configurePermissionName, resource, configureScope); + } String viewPermissionName = getViewPermissionName(client); Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); if (viewPermission == null) { @@ -139,8 +152,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa } } - private void deletePolicy(String name, ClientModel client, ResourceServer server) { - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + private void deletePolicy(String name, ResourceServer server) { + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(name, server.getId()); if (policy != null) { authz.getStoreFactory().getPolicyStore().delete(policy.getId()); } @@ -150,11 +163,12 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private void deletePermissions(ClientModel client) { ResourceServer server = resourceServer(client); if (server == null) return; - deletePolicy(getManagePermissionName(client), client, server); - deletePolicy(getViewPermissionName(client), client, server); - deletePolicy(getMapRolesPermissionName(client), client, server); - deletePolicy(getMapRolesClientScopePermissionName(client), client, server); - deletePolicy(getMapRolesCompositePermissionName(client), client, server); + deletePolicy(getManagePermissionName(client), server); + deletePolicy(getViewPermissionName(client), server); + deletePolicy(getMapRolesPermissionName(client), server); + deletePolicy(getMapRolesClientScopePermissionName(client), server); + deletePolicy(getMapRolesCompositePermissionName(client), server); + deletePolicy(getConfigurePermissionName(client), server); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); } @@ -182,6 +196,10 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.MANAGE_SCOPE, server.getId()); } + private Scope configureScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId()); + } + private Scope viewScope(ResourceServer server) { return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.VIEW_SCOPE, server.getId()); } @@ -259,6 +277,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId()); scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId()); scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId()); + scopes.put(CONFIGURE_SCOPE, configurePermission(client).getId()); return scopes; } @@ -291,6 +310,41 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return root.evaluatePermission(resource, scope, server); } + @Override + public boolean canConfigure(ClientModel client) { + if (canManage(client)) return true; + if (!root.isAdminSameRealm()) { + return false; + } + + ResourceServer server = resourceServer(client); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId()); + if (policy == null) { + return false; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return false; + } + + Scope scope = configureScope(server); + return root.evaluatePermission(resource, scope, server); + } + @Override + public void requireConfigure(ClientModel client) { + if (!canConfigure(client)) { + throw new ForbiddenException(); + } + } + + @Override public void requireManage(ClientModel client) { if (!canManage(client)) { @@ -300,7 +354,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa @Override public boolean canView(ClientModel client) { - return hasView(client) || canManage(client); + return hasView(client) || canConfigure(client); } private boolean hasView(ClientModel client) { @@ -437,6 +491,13 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); } + @Override + public Policy configurePermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId()); + } + @Override public Policy viewPermission(ClientModel client) { ResourceServer server = resourceServer(client); @@ -499,6 +560,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa Map map = new HashMap<>(); map.put("view", canView(client)); map.put("manage", canManage(client)); + map.put("configure", canConfigure(client)); return map; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java index d586c362ef..a7e9f374be 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java @@ -41,9 +41,9 @@ import java.util.Set; class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement { private static final Logger logger = Logger.getLogger(GroupPermissions.class); public static final String MAP_ROLE_SCOPE = "map-role"; - public static final String MANAGE_MEMBERSHIP_SCOPE = "manage.membership"; - public static final String MANAGE_MEMBERS_SCOPE = "manage.members"; - public static final String VIEW_MEMBERS_SCOPE = "view.members"; + public static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership"; + public static final String MANAGE_MEMBERS_SCOPE = "manage-members"; + public static final String VIEW_MEMBERS_SCOPE = "view-members"; protected final KeycloakSession session; protected final RealmModel realm; protected final AuthorizationProvider authz; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index ddc27eeab5..091d7a58f7 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -319,7 +319,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (container instanceof RealmModel) { return root.realm().canManageRealm(); } else { - return root.clients().canManage((ClientModel)container); + return root.clients().canConfigure((ClientModel)container); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 80bf07f74c..149e52678b 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -127,6 +127,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId()); scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId()); scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId()); + scopes.put(USER_IMPERSONATED_SCOPE, userImpersonatedPermission().getId()); return scopes; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 452e81fc8a..fc39267b54 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -22,9 +22,11 @@ import org.keycloak.admin.client.Keycloak; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.model.Resource; +import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.GroupModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; @@ -85,6 +87,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel salesAppsAdminRole = realm.addRole("sales-apps-admin"); salesAppsAdminRole.addCompositeRole(clientAdmin); salesAppsAdminRole.addCompositeRole(client2Admin); + ClientModel realmManagementClient = realm.getClientByClientId("realm-management"); + RoleModel queryClient = realmManagementClient.getRole(AdminRoles.QUERY_CLIENTS); UserModel admin = session.users().addUser(realm, "salesManager"); @@ -99,6 +103,10 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { admin = session.users().addUser(realm, "sales-pipeline-admin"); admin.setEnabled(true); session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password")); + admin = session.users().addUser(realm, "client-admin"); + admin.setEnabled(true); + admin.grantRole(queryClient); + session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password")); UserModel user = session.users().addUser(realm, "salesman"); user.setEnabled(true); @@ -115,6 +123,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel realmRole = realm.addRole("realm-role"); RoleModel realmRole2 = realm.addRole("realm-role2"); ClientModel client1 = realm.addClient(CLIENT_NAME); + ClientTemplateModel template = realm.addClientTemplate("template"); + client1.setFullScopeAllowed(false); RoleModel client1Role = client1.addRole("client-role"); GroupModel group = realm.createGroup("top"); @@ -280,6 +290,19 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { clientManagerPolicy.addAssociatedPolicy(userPolicy); + UserModel clientConfigurer = session.users().addUser(realm, "clientConfigurer"); + clientConfigurer.setEnabled(true); + clientConfigurer.grantRole(queryClientsRole); + session.userCredentialManager().updateCredential(realm, clientConfigurer, UserCredentialModel.password("password")); + + Policy clientConfigurePolicy = permissions.clients().configurePermission(client); + userRep = new UserPolicyRepresentation(); + userRep.setName("clientConfigure"); + userRep.addUser("clientConfigurer"); + userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client)); + clientConfigurePolicy.addAssociatedPolicy(userPolicy); + + @@ -364,20 +387,13 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { protected boolean isImportAfterEachMethod() { return true; } - //@Test + @Test public void testDemo() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupDemo); Thread.sleep(1000000000); } - //@Test - public void testUI() throws Exception { - testingClient.server().run(FineGrainAdminUnitTest::setupPolices); - testingClient.server().run(FineGrainAdminUnitTest::setupUsers); - Thread.sleep(1000000000); - } - @Test public void testEvaluationLocal() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); @@ -400,10 +416,48 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { List realmRole2Set = new LinkedList<>(); realmRole2Set.add(realmRole2); ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0); + ClientTemplateRepresentation template = adminClient.realm(TEST).clientTemplates().findAll().get(0); RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); List clientRoleSet = new LinkedList<>(); clientRoleSet.add(clientRole); + // test configure client + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "clientConfigurer", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + client.setAdminUrl("http://nowhere"); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + client.setFullScopeAllowed(true); + try { + realmClient.realm(TEST).clients().get(client.getId()).update(client); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + client.setFullScopeAllowed(false); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + + client.setClientTemplate(template.getName()); + try { + realmClient.realm(TEST).clients().get(client.getId()).update(client); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + client.setClientTemplate(null); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + + try { + realmClient.realm(TEST).clients().get(client.getId()).getScopeMappings().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + } + // test illegal impersonation { Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 94d214e766..28a985c516 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1326,9 +1326,9 @@ manage-permissions-client.tooltip=Fine grain permssions for admins that want to manage-permissions-group.tooltip=Fine grain permssions for admins that want to manage this group or the members of this group. manage-authz-group-scope-description=Policies that decide if an admin can manage this group view-authz-group-scope-description=Policies that decide if an admin can view this group -view.members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group -manage.members-authz-group-scope-description=Policies that decide if an admin can view the members of this group +view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group manage-authz-client-scope-description=Policies that decide if an admin can manage this client +configure-authz-client-scope-description=Reduced management permissions for admin. Cannot set scope, template, or protocol mappers. view-authz-client-scope-description=Policies that decide if an admin can view this client map-roles-authz-client-scope-description=Policies that decide if an admin can map roles defined by this client map-roles-client-scope-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client to the client scope of another client @@ -1336,6 +1336,11 @@ map-roles-composite-authz-client-scope-description=Policies that decide if an ad map-role-authz-role-scope-description=Policies that decide if an admin can map role this role to a user or group map-role-client-scope-authz-role-scope-description=Policies that decide if an admin can apply this role to the client scope of a client map-role-composite-authz-role-scope-description=Policies that decide if an admin can apply this role as a composite to another role - +manage-group-membership-authz-users-scope-description=Policies that decide if an admin can manage group membership for all users in the realm. This is used in conjunction with specific group policy +impersonate-authz-users-scope-description=Policies that decide if admin can impersonate other users +map-roles-authz-users-scope-description=Policies that decide if admin can map roles for all users +user-impersonated-authz-users-scope-description=Policies that decide which users can be impersonated. These policies are applied to the user being impersonated. +manage-membership-authz-group-scope-description=Policies that decide if admin can add or remove users from this group +manage-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index 86444b86bb..034d595496 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -2507,7 +2507,7 @@ module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $l }); -module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, RoleManagementPermissions, Client, Notifications) { +module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, Client, RoleManagementPermissions, Client, Notifications) { console.log('RealmRolePermissionsCtrl'); $scope.client = client; $scope.role = role; @@ -2515,6 +2515,9 @@ module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $ RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { $scope.permissions = data; }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); $scope.setEnabled = function() { console.log('perssions enabled: ' + $scope.permissions.enabled); var param = { enabled: $scope.permissions.enabled}; @@ -2542,12 +2545,15 @@ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $locat }); -module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, ClientManagementPermissions, Notifications) { +module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, Client, ClientManagementPermissions, Notifications) { $scope.client = client; $scope.realm = realm; ClientManagementPermissions.get({realm: realm.realm, client: client.id}, function(data) { $scope.permissions = data; }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); $scope.setEnabled = function() { var param = { enabled: $scope.permissions.enabled}; $scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param); diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html index bb6d7b6d58..abc21a4d90 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html @@ -27,9 +27,9 @@ - {{scopeName}} + {{scopeName}} - {{:: 'edit' | translate}} + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html index 5b423d06f9..c5f37ea320 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -28,9 +28,9 @@ - {{scopeName}} + {{scopeName}} - {{:: 'edit' | translate}} + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html index 98820eb75c..7df3a42ae0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html @@ -10,10 +10,10 @@

{{:: 'add-node' | translate}}

{{node.host|capitalize}} - +

-
+
@@ -27,7 +27,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html index e729582dae..72ff437896 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html @@ -7,7 +7,7 @@ - + {{:: 'basic-configuration' | translate}}
@@ -31,7 +31,7 @@
-
+
@@ -43,7 +43,7 @@ -
+
{{:: 'register-node-manually' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html index a2d59e1fd0..39e9ebc3a5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html @@ -1,11 +1,11 @@
- +
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html index 8fbd0792e0..82e50b78f3 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html @@ -9,7 +9,7 @@

{{:: 'generate-private-key' | translate}}

- + @@ -48,7 +48,7 @@ {{:: 'store-password.tooltip' | translate}}
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html index f96c4f0c3f..ec88cd6f65 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html @@ -9,7 +9,7 @@

{{:: 'import-client-certificate' | translate}}

- + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html index 8df035ce57..b2a5bf44b5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html @@ -1,4 +1,4 @@ -
+
@@ -63,7 +63,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html index d4fb071683..7cf5bf1359 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html @@ -1,5 +1,5 @@
- +
@@ -7,7 +7,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html index 387177491d..e6865a3e79 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html @@ -7,7 +7,7 @@ - +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 5f24b21d82..cd6e2717be 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -7,7 +7,7 @@ - +
@@ -57,7 +57,7 @@
{{:: 'client-protocol.tooltip' | translate}}
-
+
@@ -391,7 +391,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html index e8d6259b40..c74f54f4fd 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html @@ -7,7 +7,7 @@ - + @@ -137,7 +137,7 @@ kc-select-action="click" readonly>{{keyInfo.certificate}}
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html index 55a3546dbd..327b70d9ad 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html @@ -1,5 +1,5 @@
- +
@@ -7,7 +7,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html index 761b3374eb..95db767ae5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html @@ -7,7 +7,7 @@ - +
@@ -18,7 +18,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html index d21d7ef593..67c96fc930 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html @@ -10,7 +10,7 @@ - +
@@ -47,13 +47,13 @@
-
+
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html index 458b412a5d..99060743cb 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html @@ -10,7 +10,7 @@ - - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html index 753f564bd1..ec51e9674f 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html @@ -9,7 +9,7 @@

{{:: 'export-saml-key' | translate}} {{client.clientId|capitalize}}

- + @@ -55,7 +55,7 @@ {{:: 'store-password.tooltip' | translate}}
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html index b479058d66..a580a61b18 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html @@ -9,7 +9,7 @@

{{:: 'import-saml-key' | translate}} {{client.clientId|capitalize}}

- + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html index c5374bb654..776349d5be 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html @@ -7,7 +7,7 @@ - +
{{:: 'signing-key' | translate}} {{:: 'saml-signing-key' | translate}}
@@ -27,7 +27,7 @@
-
+
@@ -53,7 +53,7 @@
-
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html index 482ef5b77f..6cb01f4a47 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html @@ -25,14 +25,14 @@ {{:: 'full-scope-allowed.tooltip' | translate}}
- +
Client template has full scope allowed, which means this client will have the full scope of all roles.
- +
inherited diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html index 24b2594841..d57873f2a5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html @@ -10,7 +10,7 @@

{{client.clientId}} {{:: 'service-accounts' | translate}}

- +
@@ -102,7 +102,7 @@
-
+ diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html index cf46c2e912..e5b7c21c70 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html @@ -43,7 +43,7 @@ {{:: 'service-account-roles' | translate}} {{:: 'service-account-roles.tooltip' | translate}} -
  • +
  • {{:: 'authz-permissions' | translate}} {{:: 'manage-permissions-client.tooltip' | translate}}
  • diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html index 3aa12bc2f0..e73d998f5b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html @@ -1,7 +1,7 @@

    {{group.name|capitalize}} - +

    + @@ -29,7 +29,7 @@ {{role.description}} {{:: 'edit' | translate}}{{:: 'delete' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-roles-available' | translate}}