mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
[FGAP:V2] remove configure scope from Client resource type
Closes #38567 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
parent
279e517548
commit
6488890585
@ -108,9 +108,7 @@ set of management operations:
|
||||
| *Operation* | *Description*
|
||||
|
||||
| *view* | Defines if a realm administrator can view clients.
|
||||
| *manage* | Defines if a realm administrator can manage clients. It allows operations, such as update certificates, manage
|
||||
protocol-mappers or client scopes, regenerate access token.
|
||||
| *configure* | Defines if a realm administrator can manage clients.
|
||||
| *manage* | Defines if a realm administrator can manage clients.
|
||||
| *map-roles* | Defines if a realm administrator can assign any role defined by a client (or multiple clients) to a user.
|
||||
| *map-roles-composite* | Defines if a realm administrator can assign any role defined by a client (or multiple clients) as a composite to
|
||||
another role.
|
||||
@ -124,7 +122,6 @@ set of management operations:
|
||||
assigning only *manage* without *view* will prevent the client from being visible.
|
||||
- The *map-roles* operation does not grant the ability to manage users or assign roles arbitrarily; the administrator must also
|
||||
have user role mapping permissions.
|
||||
- For certain operations like updating or deleting clients it is required both *manage* and *configure* assigned.
|
||||
====
|
||||
|
||||
===== Roles Resource Type
|
||||
|
||||
@ -121,6 +121,7 @@ Due to fundamental changes in the permission model, **automatic migration from V
|
||||
** Example: To both view and manage a resource, both *view* and *manage* scopes for a permissions must be assigned separately.
|
||||
* Permission model changes:
|
||||
** The *user-impersonated* user permission has been _removed_.
|
||||
** The *configure* client permission has been _removed_. With the introduction of explicit operation scoping in V2, the distinction between manage and configure became ambiguous.
|
||||
* Flexible resource scoping:
|
||||
** Unlike V1, where permissions were granted either to *a single resource* (for clients, groups, and roles) or *all resources* (for users), V2 introduces greater flexibility.
|
||||
** Administrators can now define permissions for:
|
||||
|
||||
@ -75,7 +75,6 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
|
||||
public static final String VIEW = "view";
|
||||
|
||||
// client specific scopes
|
||||
public static final String CONFIGURE = "configure";
|
||||
public static final String MAP_ROLES = "map-roles";
|
||||
public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope";
|
||||
public static final String MAP_ROLES_COMPOSITE = "map-roles-composite";
|
||||
@ -95,7 +94,7 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
|
||||
|
||||
public static final String MANAGE_GROUP_MEMBERSHIP = "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 ResourceType CLIENTS = new ResourceType(CLIENTS_RESOURCE_TYPE, Set.of(MANAGE, MAP_ROLES, MAP_ROLES_CLIENT_SCOPE, MAP_ROLES_COMPOSITE, VIEW));
|
||||
public static final ResourceType GROUPS = new ResourceType(GROUPS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS));
|
||||
public static final ResourceType ROLES = new ResourceType(ROLES_RESOURCE_TYPE, Set.of(MAP_ROLE, MAP_ROLE_CLIENT_SCOPE, MAP_ROLE_COMPOSITE));
|
||||
public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP), Map.of(VIEW, Set.of(VIEW_MEMBERS), MANAGE, Set.of(MANAGE_MEMBERS)), GROUPS.getType());
|
||||
|
||||
@ -145,8 +145,6 @@ public class ClientAttributeCertificateResource {
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_ATTRIBUTE_CERTIFICATE)
|
||||
@Operation( summary = "Upload certificate and eventually private key")
|
||||
public CertificateRepresentation uploadJks() throws IOException {
|
||||
auth.clients().requireConfigure(client);
|
||||
|
||||
try {
|
||||
CertificateRepresentation info = updateCertFromRequest();
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
||||
@ -169,8 +167,6 @@ public class ClientAttributeCertificateResource {
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_ATTRIBUTE_CERTIFICATE)
|
||||
@Operation( summary = "Upload only certificate, not private key")
|
||||
public CertificateRepresentation uploadJksCertificate() throws IOException {
|
||||
auth.clients().requireConfigure(client);
|
||||
|
||||
try {
|
||||
CertificateRepresentation info = updateCertFromRequest();
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
||||
|
||||
@ -118,12 +118,14 @@ public interface ClientPermissionEvaluator {
|
||||
* <p/>
|
||||
* Or if the caller has a permission to {@link ClientPermissionManagement#CONFIGURE_SCOPE} the client.
|
||||
* <p/>
|
||||
* For V2 only: Also if the caller has a permission to {@link org.keycloak.authorization.AdminPermissionsSchema#CONFIGURE} all clients.
|
||||
* For V2 only: the call is redirected to {@code canManage(ClientModel)}.
|
||||
*/
|
||||
boolean canConfigure(ClientModel client);
|
||||
|
||||
/**
|
||||
* Throws ForbiddenException if {@link #canConfigure(ClientModel)} returns {@code false}.
|
||||
* <p/>
|
||||
* For V2 only: the call is redirected to {@code requireManage(ClientModel)}.
|
||||
*/
|
||||
void requireConfigure(ClientModel client);
|
||||
|
||||
|
||||
@ -55,10 +55,15 @@ class ClientPermissionsV2 extends ClientPermissions {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConfigure(ClientModel client) {
|
||||
if (canManage(client)) return true;
|
||||
public void requireConfigure(ClientModel client) {
|
||||
//redirecting call to manage for V2
|
||||
super.requireManage(client);
|
||||
}
|
||||
|
||||
return hasPermission(client, AdminPermissionsSchema.CONFIGURE);
|
||||
@Override
|
||||
public boolean canConfigure(ClientModel client) {
|
||||
//redirecting call to manage for V2
|
||||
return canManage(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -24,7 +24,6 @@ import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.CLIENTS;
|
||||
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;
|
||||
@ -195,64 +194,6 @@ public class ClientResourceTypeEvaluationTest extends AbstractPermissionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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(VIEW, 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<ClientScopeRepresentation> 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);
|
||||
@ -369,23 +310,17 @@ public class ClientResourceTypeEvaluationTest extends AbstractPermissionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRolesAndCompositesOnlyOneClient() {
|
||||
public void testMapRolesOnlyOneClient() {
|
||||
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
|
||||
ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
|
||||
|
||||
// create a role and sub-role
|
||||
// create a 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));
|
||||
@ -393,21 +328,58 @@ public class ClientResourceTypeEvaluationTest extends AbstractPermissionTest {
|
||||
} 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), onlyMyAdminUserPolicy);
|
||||
|
||||
// now those should pass
|
||||
realmAdminClient.realm(realm.getName()).users().get(myadmin.getId()).roles().clientLevel(myclient.getId()).add(List.of(role));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapCompositesToAnotherClientRole() {
|
||||
// to add a client role ('myclient') 'roleA' as a composite role of 'roleB' (client role of 'realmClient' client)
|
||||
// it is required to have permission to manage the `realmClient` and to map-roles-composite of the `myclient`
|
||||
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
|
||||
ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
|
||||
|
||||
// create two roles, each for seprate client
|
||||
RoleRepresentation roleA = new RoleRepresentation();
|
||||
roleA.setName("roleA");
|
||||
roleA.setClientRole(true);
|
||||
realm.admin().clients().get(myclient.getId()).roles().create(roleA);
|
||||
roleA = realm.admin().clients().get(myclient.getId()).roles().get("roleA").toRepresentation();
|
||||
|
||||
RoleRepresentation roleB = new RoleRepresentation();
|
||||
roleB.setName("roleB");
|
||||
roleB.setClientRole(true);
|
||||
realm.admin().clients().get(realmClient.getId()).roles().create(roleB);
|
||||
|
||||
// the following operations should fail as the permission wasn't granted yet
|
||||
try {
|
||||
realmAdminClient.realm(realm.getName()).clients().get(myclient.getId()).roles().get("myclient-role").addComposites(List.of(subRole));
|
||||
realmAdminClient.realm(realm.getName()).clients().get(realmClient.getId()).roles().get("roleB").addComposites(List.of(roleA));
|
||||
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);
|
||||
|
||||
createPermission(client, myclient.getId(), clientsType, Set.of(MAP_ROLES_COMPOSITE), onlyMyAdminUserPolicy);
|
||||
|
||||
// the following operations should fail as the permission to manage the realmClient is missing
|
||||
try {
|
||||
realmAdminClient.realm(realm.getName()).clients().get(realmClient.getId()).roles().get("roleB").addComposites(List.of(roleA));
|
||||
fail("Expected exception wasn't thrown.");
|
||||
} catch (Exception ex) {
|
||||
assertThat(ex, instanceOf(ForbiddenException.class));
|
||||
}
|
||||
|
||||
createPermission(client, realmClient.getId(), clientsType, Set.of(MANAGE), 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));
|
||||
realmAdminClient.realm(realm.getName()).clients().get(realmClient.getId()).roles().get("roleB").addComposites(List.of(roleA));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user