KEYCLOAK-10603 adjust assignments to roles (user-role and group-role assignments, client-scope and client "scope mappings"): allow assignments of roles which are already indirectly assigned (e.g. by composite role)

- extend RoleMapperModel with method hasDirectRole(RoleModel), which only checks for direct assignment in contrast to the existing method hasRole(RoleModel)
- extend ScopeContainerModel with method hasDirectScope(RoleModel), which only checks for direct scope mapping in contrast to the existing method hasScope(RoleModel)
- use the new hasDirectRole and hasDirectScope methods to check whether a role is in the "available" list and whether it can be assigned (previously, the hasRole method was used for this purpose)
- add hint to UI that available roles contain effectively assigned roles which are not directly assigned
- adjust and extend tests
This commit is contained in:
Daniel Fesenmeyer 2021-04-22 16:00:07 +02:00 committed by Marek Posolda
parent 8f09d34272
commit 339224578e
27 changed files with 512 additions and 119 deletions

View file

@ -77,6 +77,11 @@ public class HardcodedLDAPRoleStorageMapper extends AbstractLDAPStorageMapper {
return clientRoleMappings; return clientRoleMappings;
} }
@Override
public boolean hasDirectRole(RoleModel role) {
return super.hasDirectRole(role) || role.equals(getRole(realm));
}
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
return super.hasRole(role) || role.equals(getRole(realm)); return super.hasRole(role) || role.equals(getRole(realm));

View file

@ -625,6 +625,15 @@ public class ClientAdapter implements ClientModel, CachedObject {
updated.unregisterNode(nodeHost); updated.unregisterNode(nodeHost);
} }
@Override
public boolean hasDirectScope(RoleModel role) {
if (isUpdated()) return updated.hasDirectScope(role);
if (cached.getScope().contains(role.getId())) return true;
return getRolesStream().anyMatch(r -> Objects.equals(r, role));
}
@Override @Override
public boolean hasScope(RoleModel role) { public boolean hasScope(RoleModel role) {
if (isUpdated()) return updated.hasScope(role); if (isUpdated()) return updated.hasScope(role);
@ -633,7 +642,7 @@ public class ClientAdapter implements ClientModel, CachedObject {
if (RoleUtils.hasRole(getScopeMappingsStream(), role)) if (RoleUtils.hasRole(getScopeMappingsStream(), role))
return true; return true;
return getRolesStream().anyMatch(r -> (Objects.equals(r, role) || r.hasRole(role))); return RoleUtils.hasRole(getRolesStream(), role);
} }
@Override @Override

View file

@ -180,6 +180,13 @@ public class ClientScopeAdapter implements ClientScopeModel {
return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, cachedRealm)); return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, cachedRealm));
} }
@Override
public boolean hasDirectScope(RoleModel role) {
if (isUpdated()) return updated.hasDirectScope(role);
return cached.getScope().contains(role.getId());
}
@Override @Override
public boolean hasScope(RoleModel role) { public boolean hasScope(RoleModel role) {
if (isUpdated()) return updated.hasScope(role); if (isUpdated()) return updated.hasScope(role);

View file

@ -160,6 +160,13 @@ public class GroupAdapter implements GroupModel.Streams {
return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app));
} }
@Override
public boolean hasDirectRole(RoleModel role) {
if (isUpdated()) return updated.hasDirectRole(role);
return cached.getRoleMappings(modelSupplier).contains(role.getId());
}
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
if (isUpdated()) return updated.hasRole(role); if (isUpdated()) return updated.hasRole(role);

View file

@ -290,6 +290,12 @@ public class UserAdapter implements CachedUserModel.Streams {
return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app));
} }
@Override
public boolean hasDirectRole(RoleModel role) {
if (updated != null) return updated.hasDirectRole(role);
return cached.getRoleMappings(modelSupplier).contains(role.getId());
}
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
if (updated != null) return updated.hasRole(role); if (updated != null) return updated.hasRole(role);

View file

@ -226,7 +226,7 @@ public class GroupAdapter implements GroupModel.Streams , JpaModel<GroupEntity>
@Override @Override
public void grantRole(RoleModel role) { public void grantRole(RoleModel role) {
if (hasRole(role)) return; if (hasDirectRole(role)) return;
GroupRoleMappingEntity entity = new GroupRoleMappingEntity(); GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
entity.setGroup(getEntity()); entity.setGroup(getEntity());
entity.setRoleId(role.getId()); entity.setRoleId(role.getId());

View file

@ -446,7 +446,7 @@ public class UserAdapter implements UserModel.Streams, JpaModel<UserEntity> {
@Override @Override
public void grantRole(RoleModel role) { public void grantRole(RoleModel role) {
if (hasRole(role)) return; if (hasDirectRole(role)) return;
grantRoleImpl(role); grantRoleImpl(role);
} }

View file

@ -419,6 +419,16 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
} }
} }
@Override
public boolean hasDirectScope(RoleModel role) {
final String id = role == null ? null : role.getId();
if (id != null && this.entity.getScopeMappings().contains(id)) {
return true;
}
return getRolesStream().anyMatch(r -> (Objects.equals(r, role)));
}
@Override @Override
public boolean hasScope(RoleModel role) { public boolean hasScope(RoleModel role) {
if (isFullScopeAllowed()) return true; if (isFullScopeAllowed()) return true;

View file

@ -146,10 +146,15 @@ public class MapGroupAdapter extends AbstractGroupModel<MapGroupEntity> {
} }
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasDirectRole(RoleModel role) {
return entity.getGrantedRoles().contains(role.getId()); return entity.getGrantedRoles().contains(role.getId());
} }
@Override
public boolean hasRole(RoleModel role) {
return hasDirectRole(role);
}
@Override @Override
public void grantRole(RoleModel role) { public void grantRole(RoleModel role) {
entity.addGrantedRole(role.getId()); entity.addGrantedRole(role.getId());

View file

@ -293,10 +293,15 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
} }
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasDirectRole(RoleModel role) {
return entity.getRolesMembership().contains(role.getId()); return entity.getRolesMembership().contains(role.getId());
} }
@Override
public boolean hasRole(RoleModel role) {
return hasDirectRole(role);
}
@Override @Override
public void grantRole(RoleModel role) { public void grantRole(RoleModel role) {
entity.addRolesMembership(role.getId()); entity.addRolesMembership(role.getId());

View file

@ -538,6 +538,11 @@ public class ClientModelLazyDelegate implements ClientModel {
getDelegate().deleteScopeMapping(role); getDelegate().deleteScopeMapping(role);
} }
@Override
public boolean hasDirectScope(RoleModel role) {
return getDelegate().hasDirectScope(role);
}
@Override @Override
public boolean hasScope(RoleModel role) { public boolean hasScope(RoleModel role) {
return getDelegate().hasScope(role); return getDelegate().hasScope(role);

View file

@ -135,14 +135,14 @@ public class UpdateOnlyChangeUserModelDelegate extends UserModelDelegate {
@Override @Override
public void grantRole(RoleModel role) { public void grantRole(RoleModel role) {
if (!hasRole(role)) { if (!hasDirectRole(role)) {
delegate.grantRole(role); delegate.grantRole(role);
} }
} }
@Override @Override
public void deleteRoleMapping(RoleModel role) { public void deleteRoleMapping(RoleModel role) {
if (hasRole(role)) { if (hasDirectRole(role)) {
delegate.deleteRoleMapping(role); delegate.deleteRoleMapping(role);
} }
} }

View file

@ -18,6 +18,7 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
@ -192,6 +193,12 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
boolean isFullScopeAllowed(); boolean isFullScopeAllowed();
void setFullScopeAllowed(boolean value); void setFullScopeAllowed(boolean value);
@Override
default boolean hasDirectScope(RoleModel role) {
if (getScopeMappingsStream().anyMatch(r -> Objects.equals(r, role))) return true;
return getRolesStream().anyMatch(r -> Objects.equals(r, role));
}
boolean isPublicClient(); boolean isPublicClient();
void setPublicClient(boolean flag); void setPublicClient(boolean flag);

View file

@ -17,6 +17,7 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -62,6 +63,17 @@ public interface RoleMapperModel {
return value != null ? value.stream() : Stream.empty(); return value != null ? value.stream() : Stream.empty();
} }
/**
* Returns {@code true}, if this object is directly assigned the given role.
*
* @param role the role
* @return see description
* @see #hasRole(RoleModel) if you want to check whether this object is directly or indirectly assigned to a role
*/
default boolean hasDirectRole(RoleModel role) {
return getRoleMappingsStream().anyMatch(r -> Objects.equals(r, role));
}
/** /**
* Returns {@code true} if this object is directly or indirectly assigned the given role, {@code false} otherwise. * Returns {@code true} if this object is directly or indirectly assigned the given role, {@code false} otherwise.
* <p> * <p>
@ -73,6 +85,7 @@ public interface RoleMapperModel {
* </ul> * </ul>
* @param role * @param role
* @return see description * @return see description
* @see #hasDirectRole(RoleModel) if you want to check if this object is directly assigned to a role
*/ */
boolean hasRole(RoleModel role); boolean hasRole(RoleModel role);

View file

@ -17,6 +17,7 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -63,6 +64,26 @@ public interface ScopeContainerModel {
void deleteScopeMapping(RoleModel role); void deleteScopeMapping(RoleModel role);
/**
* Returns {@code true}, if this object has the given role directly in its scope.
*
* @param role the role
* @return see description
* @see #hasScope(RoleModel) if you want to check whether this object has the given role directly or indirectly in
* its scope
*/
default boolean hasDirectScope(RoleModel role) {
return getScopeMappingsStream().anyMatch(r -> Objects.equals(r, role));
}
/**
* Returns {@code true}, if this object has the given role directly or indirectly in its scope, {@code false}
* otherwise.
*
* @param role the role
* @return see description
* @see #hasDirectScope(RoleModel) if you want to check if this object has the given role directly in its scope
*/
boolean hasScope(RoleModel role); boolean hasScope(RoleModel role);
} }

View file

@ -24,7 +24,6 @@ import org.keycloak.models.UserModel;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**

View file

@ -177,7 +177,6 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau
return getRoleMappings().stream().filter(r -> RoleUtils.isClientRole(r, app)).collect(Collectors.toSet()); return getRoleMappings().stream().filter(r -> RoleUtils.isClientRole(r, app)).collect(Collectors.toSet());
} }
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
return RoleUtils.hasRole(getRoleMappings().stream(), role) return RoleUtils.hasRole(getRoleMappings().stream(), role)

View file

@ -134,7 +134,7 @@ public class ClientRoleMappingsResource {
return client.getRolesStream() return client.getRolesStream()
.filter(auth.roles()::canMapRole) .filter(auth.roles()::canMapRole)
.filter(((Predicate<RoleModel>) user::hasRole).negate()) .filter(((Predicate<RoleModel>) user::hasDirectRole).negate())
.map(ModelToRepresentation::toBriefRepresentation); .map(ModelToRepresentation::toBriefRepresentation);
} }

View file

@ -200,7 +200,7 @@ public class RoleMapperResource {
return realm.getRolesStream() return realm.getRolesStream()
.filter(this::canMapRole) .filter(this::canMapRole)
.filter(((Predicate<RoleModel>) roleMapper::hasRole).negate()) .filter(((Predicate<RoleModel>) roleMapper::hasDirectRole).negate())
.map(ModelToRepresentation::toBriefRepresentation); .map(ModelToRepresentation::toBriefRepresentation);
} }

View file

@ -106,7 +106,7 @@ public class ScopeMappedClientResource {
viewPermission.require(); viewPermission.require();
return scopedClient.getRolesStream() return scopedClient.getRolesStream()
.filter(((Predicate<RoleModel>) scopeContainer::hasScope).negate()) .filter(((Predicate<RoleModel>) scopeContainer::hasDirectScope).negate())
.filter(auth.roles()::canMapClientScope) .filter(auth.roles()::canMapClientScope)
.map(ModelToRepresentation::toBriefRepresentation); .map(ModelToRepresentation::toBriefRepresentation);
} }

View file

@ -155,7 +155,7 @@ public class ScopeMappedResource {
} }
return realm.getRolesStream() return realm.getRolesStream()
.filter(((Predicate<RoleModel>) scopeContainer::hasScope).negate()) .filter(((Predicate<RoleModel>) scopeContainer::hasDirectScope).negate())
.filter(auth.roles()::canMapClientScope) .filter(auth.roles()::canMapClientScope)
.map(ModelToRepresentation::toBriefRepresentation); .map(ModelToRepresentation::toBriefRepresentation);
} }

View file

@ -560,20 +560,18 @@ public class ClientTest extends AbstractAdminTest {
RoleMappingResource scopesResource = realm.clients().get(id).getScopeMappings(); RoleMappingResource scopesResource = realm.clients().get(id).getScopeMappings();
RoleRepresentation roleRep1 = RoleBuilder.create().name("role1").build(); RoleRepresentation roleRep1 = createRealmRole("realm-composite");
RoleRepresentation roleRep2 = RoleBuilder.create().name("role2").build(); RoleRepresentation roleRep2 = createRealmRole("realm-child");
realm.roles().create(roleRep1);
realm.roles().create(roleRep2);
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role1"), roleRep1, ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("realm-composite"), roleRep1, ResourceType.REALM_ROLE);
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role2"), roleRep2, ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("realm-child"), roleRep2, ResourceType.REALM_ROLE);
roleRep1 = realm.roles().get("role1").toRepresentation(); roleRep1 = realm.roles().get("realm-composite").toRepresentation();
roleRep2 = realm.roles().get("role2").toRepresentation(); roleRep2 = realm.roles().get("realm-child").toRepresentation();
realm.roles().get("role1").addComposites(Collections.singletonList(roleRep2)); realm.roles().get("realm-composite").addComposites(Collections.singletonList(roleRep2));
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath("role1"), Collections.singletonList(roleRep2), ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath("realm-composite"), Collections.singletonList(roleRep2), ResourceType.REALM_ROLE);
String accountMgmtId = realm.clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId(); String accountMgmtId = realm.clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId();
RoleRepresentation viewAccountRoleRep = realm.clients().get(accountMgmtId).roles().get(AccountRoles.VIEW_PROFILE).toRepresentation(); RoleRepresentation viewAccountRoleRep = realm.clients().get(accountMgmtId).roles().get(AccountRoles.VIEW_PROFILE).toRepresentation();
@ -584,16 +582,17 @@ public class ClientTest extends AbstractAdminTest {
scopesResource.clientLevel(accountMgmtId).add(Collections.singletonList(viewAccountRoleRep)); scopesResource.clientLevel(accountMgmtId).add(Collections.singletonList(viewAccountRoleRep));
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientScopeMappingsClientLevelPath(id, accountMgmtId), Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientScopeMappingsClientLevelPath(id, accountMgmtId), Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING);
Assert.assertNames(scopesResource.realmLevel().listAll(), "role1"); Assert.assertNames(scopesResource.realmLevel().listAll(), "realm-composite");
Assert.assertNames(scopesResource.realmLevel().listEffective(), "role1", "role2"); Assert.assertNames(scopesResource.realmLevel().listEffective(), "realm-composite", "realm-child");
Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME); Assert.assertNames(scopesResource.realmLevel().listAvailable(), "realm-child", "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll(), AccountRoles.VIEW_PROFILE);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective(), AccountRoles.VIEW_PROFILE);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS, AccountRoles.VIEW_APPLICATIONS, AccountRoles.VIEW_CONSENT, AccountRoles.MANAGE_CONSENT, AccountRoles.DELETE_ACCOUNT); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS, AccountRoles.VIEW_APPLICATIONS, AccountRoles.VIEW_CONSENT, AccountRoles.MANAGE_CONSENT, AccountRoles.DELETE_ACCOUNT);
Assert.assertNames(scopesResource.getAll().getRealmMappings(), "role1"); Assert.assertNames(scopesResource.getAll().getRealmMappings(), "realm-composite");
Assert.assertNames(scopesResource.getAll().getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.getAll().getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(), AccountRoles.VIEW_PROFILE);
scopesResource.realmLevel().remove(Collections.singletonList(roleRep1)); scopesResource.realmLevel().remove(Collections.singletonList(roleRep1));
@ -604,7 +603,7 @@ public class ClientTest extends AbstractAdminTest {
Assert.assertNames(scopesResource.realmLevel().listAll()); Assert.assertNames(scopesResource.realmLevel().listAll());
Assert.assertNames(scopesResource.realmLevel().listEffective()); Assert.assertNames(scopesResource.realmLevel().listEffective());
Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "role1", "role2", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME); Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "realm-composite", "realm-child", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll()); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll());
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.VIEW_PROFILE, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS, AccountRoles.VIEW_APPLICATIONS, AccountRoles.VIEW_CONSENT, AccountRoles.MANAGE_CONSENT, AccountRoles.DELETE_ACCOUNT); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.VIEW_PROFILE, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS, AccountRoles.VIEW_APPLICATIONS, AccountRoles.VIEW_CONSENT, AccountRoles.MANAGE_CONSENT, AccountRoles.DELETE_ACCOUNT);
@ -612,6 +611,73 @@ public class ClientTest extends AbstractAdminTest {
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective()); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective());
} }
/**
* Test for KEYCLOAK-10603.
*/
@Test
public void rolesCanBeAddedToScopeEvenWhenTheyAreAlreadyIndirectlyAssigned() {
Response response =
realm.clients().create(ClientBuilder.create().clientId("test-client").fullScopeEnabled(false).build());
String testedClientUuid = ApiUtil.getCreatedId(response);
getCleanup().addClientUuid(testedClientUuid);
response.close();
createRealmRole("realm-composite");
createRealmRole("realm-child");
realm.roles().get("realm-composite")
.addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
response = realm.clients().create(ClientBuilder.create().clientId("role-container-client").build());
String roleContainerClientUuid = ApiUtil.getCreatedId(response);
getCleanup().addClientUuid(roleContainerClientUuid);
response.close();
RoleRepresentation clientCompositeRole = RoleBuilder.create().name("client-composite").build();
realm.clients().get(roleContainerClientUuid).roles().create(clientCompositeRole);
realm.clients().get(roleContainerClientUuid).roles().create(RoleBuilder.create().name("client-child").build());
realm.clients().get(roleContainerClientUuid).roles().get("client-composite").addComposites(Collections
.singletonList(
realm.clients().get(roleContainerClientUuid).roles().get("client-child").toRepresentation()));
// Make indirect assignments: assign composite roles
RoleMappingResource scopesResource = realm.clients().get(testedClientUuid).getScopeMappings();
scopesResource.realmLevel()
.add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation()));
scopesResource.clientLevel(roleContainerClientUuid).add(Collections
.singletonList(realm.clients().get(roleContainerClientUuid).roles().get("client-composite")
.toRepresentation()));
// check state before making the direct assignments
Assert.assertNames(scopesResource.realmLevel().listAll(), "realm-composite");
Assert.assertNames(scopesResource.realmLevel().listAvailable(), "realm-child", "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME);
Assert.assertNames(scopesResource.realmLevel().listEffective(), "realm-composite", "realm-child");
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAll(), "client-composite");
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAvailable(), "client-child");
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listEffective(), "client-composite",
"client-child");
// Make direct assignments for roles which are already indirectly assigned
scopesResource.realmLevel().add(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
scopesResource.clientLevel(roleContainerClientUuid).add(Collections
.singletonList(
realm.clients().get(roleContainerClientUuid).roles().get("client-child").toRepresentation()));
// List realm roles
Assert.assertNames(scopesResource.realmLevel().listAll(), "realm-composite", "realm-child");
Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME);
Assert.assertNames(scopesResource.realmLevel().listEffective(), "realm-composite", "realm-child");
// List client roles
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAll(), "client-composite",
"client-child");
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAvailable());
Assert.assertNames(scopesResource.clientLevel(roleContainerClientUuid).listEffective(), "client-composite",
"client-child");
}
@Test @Test
public void scopesRoleRemoval() { public void scopesRoleRemoval() {
// clientA to test scope mappins // clientA to test scope mappins
@ -631,8 +697,7 @@ public class ClientTest extends AbstractAdminTest {
RoleMappingResource scopesResource = realm.clients().get(idA).getScopeMappings(); RoleMappingResource scopesResource = realm.clients().get(idA).getScopeMappings();
// create a realm role and a role in clientB // create a realm role and a role in clientB
RoleRepresentation realmRoleRep = RoleBuilder.create().name("realm-role").build(); RoleRepresentation realmRoleRep = createRealmRole("realm-role");
realm.roles().create(realmRoleRep);
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath(realmRoleRep.getName()), realmRoleRep, ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath(realmRoleRep.getName()), realmRoleRep, ResourceType.REALM_ROLE);
RoleRepresentation clientBRoleRep = RoleBuilder.create().name("clientB-role").build(); RoleRepresentation clientBRoleRep = RoleBuilder.create().name("clientB-role").build();
realm.clients().get(idB).roles().create(clientBRoleRep); realm.clients().get(idB).roles().create(clientBRoleRep);
@ -831,4 +896,13 @@ public class ClientTest extends AbstractAdminTest {
} }
} }
private RoleRepresentation createRealmRole(String roleName) {
RoleRepresentation role = RoleBuilder.create().name(roleName).build();
realm.roles().create(role);
String createdId = realm.roles().get(role.getName()).toRepresentation().getId();
getCleanup().addRoleId(createdId);
return role;
}
} }

View file

@ -2092,7 +2092,7 @@ public class UserTest extends AbstractAdminTest {
// List realm roles // List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(roles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role"); assertNames(roles.realmLevel().listAvailable(), "realm-child", "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
// List realm effective role with full representation // List realm effective role with full representation
@ -2103,7 +2103,7 @@ public class UserTest extends AbstractAdminTest {
// List client roles // List client roles
assertNames(roles.clientLevel(clientUuid).listAll(), "client-role", "client-composite"); assertNames(roles.clientLevel(clientUuid).listAll(), "client-role", "client-composite");
assertNames(roles.clientLevel(clientUuid).listAvailable(), "client-role2"); assertNames(roles.clientLevel(clientUuid).listAvailable(), "client-role2", "client-child");
assertNames(roles.clientLevel(clientUuid).listEffective(), "client-role", "client-composite", "client-child"); assertNames(roles.clientLevel(clientUuid).listEffective(), "client-role", "client-composite", "client-child");
// List client effective role with full representation // List client effective role with full representation
@ -2133,6 +2133,103 @@ public class UserTest extends AbstractAdminTest {
assertNames(roles.clientLevel(clientUuid).listAll(), "client-composite"); assertNames(roles.clientLevel(clientUuid).listAll(), "client-composite");
} }
/**
* Test for KEYCLOAK-10603.
*/
@Test
public void rolesCanBeAssignedEvenWhenTheyAreAlreadyIndirectlyAssigned() {
RealmResource realm = adminClient.realms().realm("test");
RoleRepresentation realmCompositeRole = RoleBuilder.create().name("realm-composite").build();
realm.roles().create(realmCompositeRole);
realm.roles().create(RoleBuilder.create().name("realm-child").build());
realm.roles().get("realm-composite")
.addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
realm.roles().create(RoleBuilder.create().name("realm-role-in-group").build());
Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build());
String clientUuid = ApiUtil.getCreatedId(response);
response.close();
RoleRepresentation clientCompositeRole = RoleBuilder.create().name("client-composite").build();
realm.clients().get(clientUuid).roles().create(clientCompositeRole);
realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-child").build());
realm.clients().get(clientUuid).roles().get("client-composite").addComposites(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-child").toRepresentation()));
realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-role-in-group").build());
GroupRepresentation group = GroupBuilder.create().name("mygroup").build();
response = realm.groups().add(group);
String groupId = ApiUtil.getCreatedId(response);
response.close();
response = realm.users().create(UserBuilder.create().username("myuser").build());
String userId = ApiUtil.getCreatedId(response);
response.close();
// Make indirect assignments
// .. add roles to the group and add it to the user
realm.groups().group(groupId).roles().realmLevel()
.add(Collections.singletonList(realm.roles().get("realm-role-in-group").toRepresentation()));
realm.groups().group(groupId).roles().clientLevel(clientUuid).add(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-role-in-group").toRepresentation()));
realm.users().get(userId).joinGroup(groupId);
// .. assign composite roles
RoleMappingResource userRoles = realm.users().get(userId).roles();
userRoles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation()));
userRoles.clientLevel(clientUuid).add(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-composite").toRepresentation()));
// check state before making the direct assignments
assertNames(userRoles.realmLevel().listAll(), "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(userRoles.realmLevel().listAvailable(), "realm-child", "realm-role-in-group",
"admin", "customer-user-premium", "realm-composite-role",
"sample-realm-role",
"attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group",
"user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION,
Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(userRoles.clientLevel(clientUuid).listAll(), "client-composite");
assertNames(userRoles.clientLevel(clientUuid).listAvailable(), "client-child",
"client-role-in-group");
assertNames(userRoles.clientLevel(clientUuid).listEffective(), "client-composite", "client-child",
"client-role-in-group");
// Make direct assignments for roles which are already indirectly assigned
userRoles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
userRoles.realmLevel()
.add(Collections.singletonList(realm.roles().get("realm-role-in-group").toRepresentation()));
userRoles.clientLevel(clientUuid).add(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-child").toRepresentation()));
userRoles.clientLevel(clientUuid).add(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-role-in-group").toRepresentation()));
// List realm roles
assertNames(userRoles.realmLevel().listAll(), "realm-composite",
"realm-child", "realm-role-in-group", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(userRoles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role",
"sample-realm-role", "attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group",
"user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION,
Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
// List client roles
assertNames(userRoles.clientLevel(clientUuid).listAll(), "client-composite", "client-child",
"client-role-in-group");
assertNames(userRoles.clientLevel(clientUuid).listAvailable());
assertNames(userRoles.clientLevel(clientUuid).listEffective(), "client-composite", "client-child",
"client-role-in-group");
// Get mapping representation
MappingsRepresentation all = userRoles.getAll();
assertNames(all.getRealmMappings(), "realm-composite",
"realm-child", "realm-role-in-group", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertEquals(1, all.getClientMappings().size());
assertNames(all.getClientMappings().get("myclient").getMappings(), "client-composite", "client-child",
"client-role-in-group");
}
@Test @Test
public void defaultMaxResults() { public void defaultMaxResults() {
UsersResource users = adminClient.realms().realm("test").users(); UsersResource users = adminClient.realms().realm("test").users();

View file

@ -22,6 +22,7 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopesResource; import org.keycloak.admin.client.resource.ClientScopesResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource; import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
@ -38,19 +39,18 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.Matchers; import org.keycloak.testsuite.util.Matchers;
import org.keycloak.testsuite.util.RoleBuilder;
import javax.ws.rs.ClientErrorException; import javax.ws.rs.ClientErrorException;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -58,6 +58,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.Assert.assertNames;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -209,15 +210,12 @@ public class ClientScopeTest extends AbstractClientTest {
@Test @Test
public void testScopes() { public void testScopes() {
// Add realm role1 RoleRepresentation realmCompositeRole = createRealmRole("realm-composite");
RoleRepresentation roleRep1 = createRealmRole("role1"); RoleRepresentation realmChildRole = createRealmRole("realm-child");
testRealmResource().roles().get("realm-composite").addComposites(Collections.singletonList(realmChildRole));
// Add realm role2 assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE,
RoleRepresentation roleRep2 = createRealmRole("role2"); AdminEventPaths.roleResourceCompositesPath("realm-composite"),
Collections.singletonList(realmChildRole), ResourceType.REALM_ROLE);
// Add role2 as composite to role1
testRealmResource().roles().get("role1").addComposites(Collections.singletonList(roleRep2));
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath("role1"), Collections.singletonList(roleRep2), ResourceType.REALM_ROLE);
// create client scope // create client scope
ClientScopeRepresentation scopeRep = new ClientScopeRepresentation(); ClientScopeRepresentation scopeRep = new ClientScopeRepresentation();
@ -225,15 +223,21 @@ public class ClientScopeTest extends AbstractClientTest {
String scopeId = createClientScope(scopeRep); String scopeId = createClientScope(scopeRep);
// update with some scopes // update with some scopes
String accountMgmtId = testRealmResource().clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId(); String accountMgmtId =
RoleRepresentation viewAccountRoleRep = testRealmResource().clients().get(accountMgmtId).roles().get(AccountRoles.VIEW_PROFILE).toRepresentation(); testRealmResource().clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId();
RoleRepresentation viewAccountRoleRep = testRealmResource().clients().get(accountMgmtId).roles()
.get(AccountRoles.VIEW_PROFILE).toRepresentation();
RoleMappingResource scopesResource = clientScopes().get(scopeId).getScopeMappings(); RoleMappingResource scopesResource = clientScopes().get(scopeId).getScopeMappings();
scopesResource.realmLevel().add(Collections.singletonList(roleRep1)); scopesResource.realmLevel().add(Collections.singletonList(realmCompositeRole));
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientScopeRoleMappingsRealmLevelPath(scopeId), Collections.singletonList(roleRep1), ResourceType.REALM_SCOPE_MAPPING); assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE,
AdminEventPaths.clientScopeRoleMappingsRealmLevelPath(scopeId),
Collections.singletonList(realmCompositeRole), ResourceType.REALM_SCOPE_MAPPING);
scopesResource.clientLevel(accountMgmtId).add(Collections.singletonList(viewAccountRoleRep)); scopesResource.clientLevel(accountMgmtId).add(Collections.singletonList(viewAccountRoleRep));
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientScopeRoleMappingsClientLevelPath(scopeId, accountMgmtId), Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING); assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE,
AdminEventPaths.clientScopeRoleMappingsClientLevelPath(scopeId, accountMgmtId),
Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING);
// test that scopes are available (also through composite role) // test that scopes are available (also through composite role)
List<RoleRepresentation> allRealm = scopesResource.realmLevel().listAll(); List<RoleRepresentation> allRealm = scopesResource.realmLevel().listAll();
@ -241,61 +245,110 @@ public class ClientScopeTest extends AbstractClientTest {
List<RoleRepresentation> effectiveRealm = scopesResource.realmLevel().listEffective(); List<RoleRepresentation> effectiveRealm = scopesResource.realmLevel().listEffective();
List<RoleRepresentation> accountRoles = scopesResource.clientLevel(accountMgmtId).listAll(); List<RoleRepresentation> accountRoles = scopesResource.clientLevel(accountMgmtId).listAll();
assertRolesPresent(allRealm, "role1"); assertNames(allRealm, "realm-composite");
assertRolesNotPresent(availableRealm, "role1", "role2"); assertNames(availableRealm, "realm-child", Constants.OFFLINE_ACCESS_ROLE, Constants.AUTHZ_UMA_AUTHORIZATION,
assertRolesPresent(effectiveRealm, "role1", "role2"); Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertRolesPresent(accountRoles, AccountRoles.VIEW_PROFILE); assertNames(effectiveRealm, "realm-composite", "realm-child");
assertNames(accountRoles, AccountRoles.VIEW_PROFILE);
MappingsRepresentation mappingsRep = clientScopes().get(scopeId).getScopeMappings().getAll(); MappingsRepresentation mappingsRep = clientScopes().get(scopeId).getScopeMappings().getAll();
assertRolesPresent(mappingsRep.getRealmMappings(), "role1"); assertNames(mappingsRep.getRealmMappings(), "realm-composite");
assertRolesPresent(mappingsRep.getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(), AccountRoles.VIEW_PROFILE); assertNames(mappingsRep.getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(),
AccountRoles.VIEW_PROFILE);
// remove scopes // remove scopes
scopesResource.realmLevel().remove(Collections.singletonList(roleRep1)); scopesResource.realmLevel().remove(Collections.singletonList(realmCompositeRole));
assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE, AdminEventPaths.clientScopeRoleMappingsRealmLevelPath(scopeId), Collections.singletonList(roleRep1), ResourceType.REALM_SCOPE_MAPPING); assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE,
AdminEventPaths.clientScopeRoleMappingsRealmLevelPath(scopeId),
Collections.singletonList(realmCompositeRole), ResourceType.REALM_SCOPE_MAPPING);
scopesResource.clientLevel(accountMgmtId).remove(Collections.singletonList(viewAccountRoleRep)); scopesResource.clientLevel(accountMgmtId).remove(Collections.singletonList(viewAccountRoleRep));
assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE, AdminEventPaths.clientScopeRoleMappingsClientLevelPath(scopeId, accountMgmtId), Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING); assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE,
AdminEventPaths.clientScopeRoleMappingsClientLevelPath(scopeId, accountMgmtId),
Collections.singletonList(viewAccountRoleRep), ResourceType.CLIENT_SCOPE_MAPPING);
// assert scopes are removed // assert scopes are removed
allRealm = scopesResource.realmLevel().listAll(); allRealm = scopesResource.realmLevel().listAll();
availableRealm = scopesResource.realmLevel().listAvailable(); availableRealm = scopesResource.realmLevel().listAvailable();
effectiveRealm = scopesResource.realmLevel().listEffective(); effectiveRealm = scopesResource.realmLevel().listEffective();
accountRoles = scopesResource.clientLevel(accountMgmtId).listAll(); accountRoles = scopesResource.clientLevel(accountMgmtId).listAll();
assertRolesNotPresent(allRealm, "role1"); assertNames(allRealm);
assertRolesPresent(availableRealm, "role1", "role2"); assertNames(availableRealm, "realm-composite", "realm-child", Constants.OFFLINE_ACCESS_ROLE,
assertRolesNotPresent(effectiveRealm, "role1", "role2"); Constants.AUTHZ_UMA_AUTHORIZATION,
assertRolesNotPresent(accountRoles, AccountRoles.VIEW_PROFILE); Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(effectiveRealm);
assertNames(accountRoles);
// remove scope // remove scope
removeClientScope(scopeId); removeClientScope(scopeId);
} }
private void assertRolesPresent(List<RoleRepresentation> roles, String... expectedRoleNames) { /**
String[] expectedList = expectedRoleNames; * Test for KEYCLOAK-10603.
*/
@Test
public void rolesCanBeAddedToScopeEvenWhenTheyAreAlreadyIndirectlyAssigned() {
RealmResource realm = testRealmResource();
ClientScopeRepresentation clientScopeRep = new ClientScopeRepresentation();
clientScopeRep.setName("my-scope");
String clientScopeId = createClientScope(clientScopeRep);
Set<String> presentRoles = new HashSet<>(); createRealmRole("realm-composite");
for (RoleRepresentation roleRep : roles) { createRealmRole("realm-child");
presentRoles.add(roleRep.getName()); realm.roles().get("realm-composite")
} .addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
for (String expected : expectedList) { Response response = realm.clients().create(ClientBuilder.create().clientId("role-container-client").build());
if (!presentRoles.contains(expected)) { String roleContainerClientUuid = ApiUtil.getCreatedId(response);
Assert.fail("Expected role " + expected + " not available"); getCleanup().addClientUuid(roleContainerClientUuid);
} response.close();
}
RoleRepresentation clientCompositeRole = RoleBuilder.create().name("client-composite").build();
realm.clients().get(roleContainerClientUuid).roles().create(clientCompositeRole);
realm.clients().get(roleContainerClientUuid).roles().create(RoleBuilder.create().name("client-child").build());
realm.clients().get(roleContainerClientUuid).roles().get("client-composite").addComposites(Collections
.singletonList(
realm.clients().get(roleContainerClientUuid).roles().get("client-child").toRepresentation()));
// Make indirect assignments: assign composite roles
RoleMappingResource scopesResource = realm.clientScopes().get(clientScopeId).getScopeMappings();
scopesResource.realmLevel()
.add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation()));
scopesResource.clientLevel(roleContainerClientUuid).add(Collections
.singletonList(realm.clients().get(roleContainerClientUuid).roles().get("client-composite")
.toRepresentation()));
// check state before making the direct assignments
assertNames(scopesResource.realmLevel().listAll(), "realm-composite");
assertNames(scopesResource.realmLevel().listAvailable(), "realm-child", "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(scopesResource.realmLevel().listEffective(), "realm-composite", "realm-child");
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAll(), "client-composite");
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAvailable(), "client-child");
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listEffective(), "client-composite",
"client-child");
// Make direct assignments for roles which are already indirectly assigned
scopesResource.realmLevel().add(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
scopesResource.clientLevel(roleContainerClientUuid).add(Collections
.singletonList(
realm.clients().get(roleContainerClientUuid).roles().get("client-child").toRepresentation()));
// List realm roles
assertNames(scopesResource.realmLevel().listAll(), "realm-composite", "realm-child");
assertNames(scopesResource.realmLevel().listAvailable(), "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(scopesResource.realmLevel().listEffective(), "realm-composite", "realm-child");
// List client roles
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAll(), "client-composite",
"client-child");
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listAvailable());
assertNames(scopesResource.clientLevel(roleContainerClientUuid).listEffective(), "client-composite",
"client-child");
} }
private void assertRolesNotPresent(List<RoleRepresentation> roles, String... notExpectedRoleNames) {
List<String> notExpectedList = Arrays.asList(notExpectedRoleNames);
for (RoleRepresentation roleRep : roles) {
if (notExpectedList.contains(roleRep.getName())) {
Assert.fail("Role " + roleRep.getName() + " wasn't expected to be available");
}
}
}
// KEYCLOAK-2809 // KEYCLOAK-2809
@Test @Test
public void testRemoveScopedRole() { public void testRemoveScopedRole() {
@ -334,7 +387,11 @@ public class ClientScopeTest extends AbstractClientTest {
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.roleResourcePath(roleName), roleRep, ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.roleResourcePath(roleName), roleRep, ResourceType.REALM_ROLE);
return testRealmResource().roles().get(roleName).toRepresentation(); RoleRepresentation createdRole = testRealmResource().roles().get(roleName).toRepresentation();
getCleanup().addRoleId(createdRole.getId());
return createdRole;
} }
@Test @Test

View file

@ -170,6 +170,14 @@ public class GroupTest extends AbstractGroupTest {
} }
} }
private RoleRepresentation createRealmRole(RealmResource realm, RoleRepresentation role) {
realm.roles().create(role);
RoleRepresentation created = realm.roles().get(role.getName()).toRepresentation();
getCleanup().addRoleId(created.getId());
return created;
}
@Test @Test
public void doNotAllowSameGroupNameAtSameLevel() throws Exception { public void doNotAllowSameGroupNameAtSameLevel() throws Exception {
RealmResource realm = adminClient.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");
@ -363,24 +371,9 @@ public class GroupTest extends AbstractGroupTest {
@Test @Test
public void createAndTestGroups() throws Exception { public void createAndTestGroups() throws Exception {
RealmResource realm = adminClient.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");
{ RoleRepresentation topRole = createRealmRole(realm, RoleBuilder.create().name("topRole").build());
RoleRepresentation groupRole = new RoleRepresentation(); RoleRepresentation level2Role = createRealmRole(realm, RoleBuilder.create().name("level2Role").build());
groupRole.setName("topRole"); RoleRepresentation level3Role = createRealmRole(realm, RoleBuilder.create().name("level3Role").build());
realm.roles().create(groupRole);
}
RoleRepresentation topRole = realm.roles().get("topRole").toRepresentation();
{
RoleRepresentation groupRole = new RoleRepresentation();
groupRole.setName("level2Role");
realm.roles().create(groupRole);
}
RoleRepresentation level2Role = realm.roles().get("level2Role").toRepresentation();
{
RoleRepresentation groupRole = new RoleRepresentation();
groupRole.setName("level3Role");
realm.roles().create(groupRole);
}
RoleRepresentation level3Role = realm.roles().get("level3Role").toRepresentation();
// Role events tested elsewhere // Role events tested elsewhere
assertAdminEvents.clear(); assertAdminEvents.clear();
@ -689,13 +682,14 @@ public class GroupTest extends AbstractGroupTest {
@Test @Test
public void roleMappings() { public void roleMappings() {
RealmResource realm = adminClient.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");
realm.roles().create(RoleBuilder.create().name("realm-role").build()); createRealmRole(realm, RoleBuilder.create().name("realm-role").build());
realm.roles().create(RoleBuilder.create().name("realm-composite").build()); createRealmRole(realm, RoleBuilder.create().name("realm-composite").build());
realm.roles().create(RoleBuilder.create().name("realm-child").build()); createRealmRole(realm, RoleBuilder.create().name("realm-child").build());
realm.roles().get("realm-composite").addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation())); realm.roles().get("realm-composite").addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
try (Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build())) { try (Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build())) {
String clientId = ApiUtil.getCreatedId(response); String clientId = ApiUtil.getCreatedId(response);
getCleanup().addClientUuid(clientId);
realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-role").build()); realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-role").build());
realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-role2").build()); realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-role2").build());
@ -731,12 +725,12 @@ public class GroupTest extends AbstractGroupTest {
// List realm roles // List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite"); assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite");
assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); assertNames(roles.realmLevel().listAvailable(), "realm-child", "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child"); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child");
// List client roles // List client roles
assertNames(roles.clientLevel(clientId).listAll(), "client-role", "client-composite"); assertNames(roles.clientLevel(clientId).listAll(), "client-role", "client-composite");
assertNames(roles.clientLevel(clientId).listAvailable(), "client-role2"); assertNames(roles.clientLevel(clientId).listAvailable(), "client-role2", "client-child");
assertNames(roles.clientLevel(clientId).listEffective(), "client-role", "client-composite", "client-child"); assertNames(roles.clientLevel(clientId).listEffective(), "client-role", "client-composite", "client-child");
// Get mapping representation // Get mapping representation
@ -759,6 +753,79 @@ public class GroupTest extends AbstractGroupTest {
} }
} }
/**
* Test for KEYCLOAK-10603.
*/
@Test
public void rolesCanBeAssignedEvenWhenTheyAreAlreadyIndirectlyAssigned() {
RealmResource realm = adminClient.realms().realm("test");
createRealmRole(realm, RoleBuilder.create().name("realm-composite").build());
createRealmRole(realm, RoleBuilder.create().name("realm-child").build());
realm.roles().get("realm-composite")
.addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
try (Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build())) {
String clientId = ApiUtil.getCreatedId(response);
getCleanup().addClientUuid(clientId);
realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-composite").build());
realm.clients().get(clientId).roles().create(RoleBuilder.create().name("client-child").build());
realm.clients().get(clientId).roles().get("client-composite").addComposites(Collections
.singletonList(realm.clients().get(clientId).roles().get("client-child").toRepresentation()));
GroupRepresentation group = new GroupRepresentation();
group.setName("group");
// Roles+clients tested elsewhere
assertAdminEvents.clear();
String groupId = createGroup(realm, group).getId();
RoleMappingResource roles = realm.groups().group(groupId).roles();
// Make indirect assignments: assign composite roles
roles.realmLevel()
.add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation()));
RoleRepresentation clientComposite =
realm.clients().get(clientId).roles().get("client-composite").toRepresentation();
roles.clientLevel(clientId).add(Collections.singletonList(clientComposite));
// Check state before making the direct assignments
assertNames(roles.realmLevel().listAll(), "realm-composite");
assertNames(roles.realmLevel().listAvailable(), "realm-child", "admin", "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role",
"sample-realm-role", "attribute-role", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(roles.realmLevel().listEffective(), "realm-composite", "realm-child");
assertNames(roles.clientLevel(clientId).listAll(), "client-composite");
assertNames(roles.clientLevel(clientId).listAvailable(), "client-child");
assertNames(roles.clientLevel(clientId).listEffective(), "client-composite", "client-child");
// Make direct assignments for roles which are already indirectly assigned
roles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-child").toRepresentation()));
RoleRepresentation clientChild =
realm.clients().get(clientId).roles().get("client-child").toRepresentation();
roles.clientLevel(clientId).add(Collections.singletonList(clientChild));
// List realm roles
assertNames(roles.realmLevel().listAll(), "realm-composite", "realm-child");
assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access",
Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role",
"sample-realm-role", "attribute-role", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(roles.realmLevel().listEffective(), "realm-composite", "realm-child");
// List client roles
assertNames(roles.clientLevel(clientId).listAll(), "client-composite", "client-child");
assertNames(roles.clientLevel(clientId).listAvailable());
assertNames(roles.clientLevel(clientId).listEffective(), "client-composite", "client-child");
// Get mapping representation
MappingsRepresentation all = roles.getAll();
assertNames(all.getRealmMappings(), "realm-composite", "realm-child");
assertEquals(1, all.getClientMappings().size());
assertNames(all.getClientMappings().get("myclient").getMappings(), "client-composite", "client-child");
}
}
/** /**
* Verifies that the user does not have access to Keycloak Admin endpoint when role is not * Verifies that the user does not have access to Keycloak Admin endpoint when role is not

View file

@ -440,7 +440,7 @@ type=Typ
#scope-mappings=Scope Mappings #scope-mappings=Scope Mappings
#full-scope-allowed=Full Scope Allowed #full-scope-allowed=Full Scope Allowed
#full-scope-allowed.tooltip=Allows you to disable all restrictions. #full-scope-allowed.tooltip=Allows you to disable all restrictions.
#scope.available-roles.tooltip=Realm level roles that can be assigned to scope. #scope.available-roles.tooltip=Realm level roles that can be assigned to scope. Contains effectively assigned roles which are not directly assigned.
assigned-roles=Zugewiesene Rollen assigned-roles=Zugewiesene Rollen
#assigned-roles.tooltip=Realm level roles assigned to scope. #assigned-roles.tooltip=Realm level roles assigned to scope.
effective-roles=Effektive Rollen effective-roles=Effektive Rollen
@ -494,7 +494,7 @@ last-refresh=Letzte Aktualisierung
#encryption-key=Encryption Key #encryption-key=Encryption Key
#saml-encryption-key.tooltip=SAML Encryption Key. #saml-encryption-key.tooltip=SAML Encryption Key.
#service-accounts=Service Accounts #service-accounts=Service Accounts
#service-account.available-roles.tooltip=Realm level roles that can be assigned to service account. #service-account.available-roles.tooltip=Realm level roles that can be assigned to service account. Contains effectively assigned roles which are not directly assigned.
#service-account.assigned-roles.tooltip=Realm level roles assigned to service account. #service-account.assigned-roles.tooltip=Realm level roles assigned to service account.
#service-account-is-not-enabled-for=Service account is not enabled for {{client}} #service-account-is-not-enabled-for=Service account is not enabled for {{client}}
#create-protocol-mappers=Create Protocol Mappers #create-protocol-mappers=Create Protocol Mappers
@ -713,7 +713,7 @@ groups=Gruppen
group.add-selected.tooltip=Realm-Rollen die zu der Gruppen hinzugef\u00FCgt werden k\u00F6nnen. group.add-selected.tooltip=Realm-Rollen die zu der Gruppen hinzugef\u00FCgt werden k\u00F6nnen.
group.assigned-roles.tooltip=Realm-Rollen die zur Gruppe zugeordnet sind group.assigned-roles.tooltip=Realm-Rollen die zur Gruppe zugeordnet sind
#group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role. #group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
#group.available-roles.tooltip=Assignable roles from this client. #group.available-roles.tooltip=Assignable roles from this client. Contains effectively assigned roles which are not directly assigned.
#group.assigned-roles-client.tooltip=Role mappings for this client. #group.assigned-roles-client.tooltip=Role mappings for this client.
#group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. #group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
@ -737,7 +737,7 @@ users=Benutzer
user.add-selected.tooltip=Realm-Rollen, die dem Benutzer zugewiesen werden k\u00F6nnen. user.add-selected.tooltip=Realm-Rollen, die dem Benutzer zugewiesen werden k\u00F6nnen.
user.assigned-roles.tooltip=Realm-Rollen, die dem Benutzer zugewiesen sind. user.assigned-roles.tooltip=Realm-Rollen, die dem Benutzer zugewiesen sind.
user.effective-roles.tooltip=Alle Realm-Rollen-Zuweisungen. Einige Rollen hier k\u00F6nnen von zusammengesetzten Rollen geerbt sein. user.effective-roles.tooltip=Alle Realm-Rollen-Zuweisungen. Einige Rollen hier k\u00F6nnen von zusammengesetzten Rollen geerbt sein.
#user.available-roles.tooltip=Assignable roles from this client. #user.available-roles.tooltip=Assignable roles from this client. Contains effectively assigned roles which are not directly assigned.
#user.assigned-roles-client.tooltip=Role mappings for this client. #user.assigned-roles-client.tooltip=Role mappings for this client.
#user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. #user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.

View file

@ -545,13 +545,13 @@ add-builtin-protocol-mapper=Add Builtin Protocol Mapper
scope-mappings=Scope Mappings scope-mappings=Scope Mappings
full-scope-allowed=Full Scope Allowed full-scope-allowed=Full Scope Allowed
full-scope-allowed.tooltip=Allows you to disable all restrictions. full-scope-allowed.tooltip=Allows you to disable all restrictions.
scope.available-roles.tooltip=Realm level roles that can be assigned to scope. scope.available-roles.tooltip=Realm level roles that can be assigned to scope. Contains effectively assigned roles which are not directly assigned.
assigned-roles=Assigned Roles assigned-roles=Assigned Roles
assigned-roles.tooltip=Realm level roles assigned to scope. assigned-roles.tooltip=Realm level roles assigned to scope.
effective-roles=Effective Roles effective-roles=Effective Roles
realm.effective-roles.tooltip=Assigned realm level roles that may have been inherited from a composite role. realm.effective-roles.tooltip=Assigned realm level roles that may have been inherited from a composite role.
select-client-roles.tooltip=Select client to view roles for client select-client-roles.tooltip=Select client to view roles for client
assign.available-roles.tooltip=Client roles available to be assigned. assign.available-roles.tooltip=Client roles available to be assigned. Contains effectively assigned roles which are not directly assigned.
client.assigned-roles.tooltip=Assigned client roles. client.assigned-roles.tooltip=Assigned client roles.
client.effective-roles.tooltip=Assigned client roles that may have been inherited from a composite role. client.effective-roles.tooltip=Assigned client roles that may have been inherited from a composite role.
basic-configuration=Basic configuration basic-configuration=Basic configuration
@ -599,7 +599,7 @@ export=Export
encryption-key=Encryption Key encryption-key=Encryption Key
saml-encryption-key.tooltip=SAML Encryption Key. saml-encryption-key.tooltip=SAML Encryption Key.
service-accounts=Service Accounts service-accounts=Service Accounts
service-account.available-roles.tooltip=Realm level roles that can be assigned to service account. service-account.available-roles.tooltip=Realm level roles that can be assigned to service account. Contains effectively assigned roles which are not directly assigned.
service-account.assigned-roles.tooltip=Realm level roles assigned to service account. service-account.assigned-roles.tooltip=Realm level roles assigned to service account.
service-account-is-not-enabled-for=Service account is not enabled for {{client}} service-account-is-not-enabled-for=Service account is not enabled for {{client}}
create-protocol-mappers=Create Protocol Mappers create-protocol-mappers=Create Protocol Mappers
@ -917,10 +917,10 @@ client-updater-source-roles.tooltip=The condition is checked during client regis
groups=Groups groups=Groups
group.add-selected.tooltip=Realm roles that can be assigned to the group. group.add-selected.tooltip=Realm roles that can be assigned to the group. Contains effectively assigned roles which are not directly assigned.
group.assigned-roles.tooltip=Realm roles mapped to the group group.assigned-roles.tooltip=Realm roles mapped to the group
group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role. group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
group.available-roles.tooltip=Assignable roles from this client. group.available-roles.tooltip=Assignable roles from this client. Contains effectively assigned roles which are not directly assigned.
group.assigned-roles-client.tooltip=Role mappings for this client. group.assigned-roles-client.tooltip=Role mappings for this client.
group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
@ -941,10 +941,10 @@ default-roles=Default Roles
no-realm-roles-available=No realm roles available no-realm-roles-available=No realm roles available
users=Users users=Users
user.add-selected.tooltip=Realm roles that can be assigned to the user. user.add-selected.tooltip=Realm roles that can be assigned to the user. Contains effectively assigned roles which are not directly assigned.
user.assigned-roles.tooltip=Realm roles mapped to the user user.assigned-roles.tooltip=Realm roles mapped to the user
user.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role. user.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
user.available-roles.tooltip=Assignable roles from this client. user.available-roles.tooltip=Assignable roles from this client. Contains effectively assigned roles which are not directly assigned.
user.assigned-roles-client.tooltip=Role mappings for this client. user.assigned-roles-client.tooltip=Role mappings for this client.
user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.