KEYCLOAK-12754 Honor nested composite roles when creating roles via REST API (#7097)
* KEYCLOAK-12754 Honor nested composite roles when creating roles via REST API - Validate composite roles when creating roles via REST API
This commit is contained in:
parent
982f0f93b4
commit
b1bcd5d66e
2 changed files with 100 additions and 1 deletions
|
@ -28,7 +28,10 @@ import org.keycloak.models.ModelDuplicateException;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.ManagementPermissionReference;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
|
@ -52,10 +55,13 @@ import javax.ws.rs.QueryParam;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
|
||||
|
@ -148,6 +154,46 @@ public class RoleContainerResource extends RoleResource {
|
|||
adminEvent.resource(ResourceType.REALM_ROLE);
|
||||
}
|
||||
|
||||
// Handling of nested composite roles for KEYCLOAK-12754
|
||||
if (rep.isComposite() && rep.getComposites() != null) {
|
||||
RoleRepresentation.Composites composites = rep.getComposites();
|
||||
|
||||
Set<String> compositeRealmRoles = composites.getRealm();
|
||||
if (compositeRealmRoles != null && !compositeRealmRoles.isEmpty()) {
|
||||
Set<RoleModel> realmRoles = new LinkedHashSet<>();
|
||||
for (String roleName : compositeRealmRoles) {
|
||||
RoleModel realmRole = realm.getRole(roleName);
|
||||
if (realmRole == null) {
|
||||
return ErrorResponse.error("Realm Role with name " + roleName + " does not exist", Response.Status.NOT_FOUND);
|
||||
}
|
||||
realmRoles.add(realmRole);
|
||||
}
|
||||
RoleUtils.expandCompositeRoles(realmRoles).forEach(role::addCompositeRole);
|
||||
}
|
||||
|
||||
Map<String, List<String>> compositeClientRoles = composites.getClient();
|
||||
if (compositeClientRoles != null && !compositeClientRoles.isEmpty()) {
|
||||
Set<Map.Entry<String, List<String>>> entries = compositeClientRoles.entrySet();
|
||||
for (Map.Entry<String, List<String>> clientIdWithClientRoleNames : entries) {
|
||||
String clientId = clientIdWithClientRoleNames.getKey();
|
||||
List<String> clientRoleNames = clientIdWithClientRoleNames.getValue();
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
continue;
|
||||
}
|
||||
Set<RoleModel> clientRoles = new LinkedHashSet<>();
|
||||
for (String roleName : clientRoleNames) {
|
||||
RoleModel clientRole = client.getRole(roleName);
|
||||
if (clientRole == null) {
|
||||
return ErrorResponse.error("Client Role with name " + roleName + " does not exist", Response.Status.NOT_FOUND);
|
||||
}
|
||||
clientRoles.add(clientRole);
|
||||
}
|
||||
RoleUtils.expandCompositeRoles(clientRoles).forEach(role::addCompositeRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getName()).representation(rep).success();
|
||||
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(role.getName()).build()).build();
|
||||
|
|
|
@ -38,6 +38,8 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -56,6 +58,9 @@ public class RoleByIdResourceTest extends AbstractAdminTest {
|
|||
private RoleByIdResource resource;
|
||||
|
||||
private Map<String, String> ids = new HashMap<>();
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String clientUuid;
|
||||
|
||||
@Before
|
||||
|
@ -63,7 +68,8 @@ public class RoleByIdResourceTest extends AbstractAdminTest {
|
|||
adminClient.realm(REALM_NAME).roles().create(RoleBuilder.create().name("role-a").description("Role A").build());
|
||||
adminClient.realm(REALM_NAME).roles().create(RoleBuilder.create().name("role-b").description("Role B").build());
|
||||
|
||||
Response response = adminClient.realm(REALM_NAME).clients().create(ClientBuilder.create().clientId("client-a").build());
|
||||
clientId = "client-a";
|
||||
Response response = adminClient.realm(REALM_NAME).clients().create(ClientBuilder.create().clientId(clientId).build());
|
||||
clientUuid = ApiUtil.getCreatedId(response);
|
||||
getCleanup().addClientUuid(clientUuid);
|
||||
response.close();
|
||||
|
@ -172,6 +178,53 @@ public class RoleByIdResourceTest extends AbstractAdminTest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* see KEYCLOAK-12754
|
||||
*/
|
||||
@Test
|
||||
public void createNewMixedRealmCompositeRole() {
|
||||
|
||||
RoleRepresentation newRoleComp = RoleBuilder.create().name("role-mixed-comp").composite().realmComposite("role-a").clientComposite(clientId, "role-c").build();
|
||||
adminClient.realm(REALM_NAME).roles().create(newRoleComp);
|
||||
|
||||
RoleRepresentation roleMixedComp = adminClient.realm(REALM_NAME).roles().get(newRoleComp.getName()).toRepresentation();
|
||||
assertTrue(roleMixedComp.isComposite());
|
||||
|
||||
Predicate<RoleRepresentation> isClientRole = RoleRepresentation::getClientRole;
|
||||
|
||||
Set<RoleRepresentation> roleComposites = resource.getRoleComposites(roleMixedComp.getId());
|
||||
Set<RoleRepresentation> containedRealmRoles = roleComposites.stream().filter(isClientRole.negate()).collect(Collectors.toSet());
|
||||
assertFalse(containedRealmRoles.isEmpty());
|
||||
assertTrue(containedRealmRoles.stream().anyMatch(r -> r.getName().equals("role-a")));
|
||||
|
||||
Set<RoleRepresentation> containedClientRoles = roleComposites.stream().filter(isClientRole).collect(Collectors.toSet());
|
||||
assertFalse(containedClientRoles.isEmpty());
|
||||
assertTrue(containedClientRoles.stream().anyMatch(r -> r.getContainerId().equals(clientUuid) && r.getName().equals("role-c")));
|
||||
}
|
||||
|
||||
/**
|
||||
* see KEYCLOAK-12754
|
||||
*/
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void createNewMixedRealmCompositeRoleWithUnknownRealmRoleShouldThrow() {
|
||||
|
||||
String unknownRealmRole = "realm-role-unknown";
|
||||
RoleRepresentation newRoleComp = RoleBuilder.create().name("role-broken-comp1").composite().realmComposite(unknownRealmRole).clientComposite(clientId, "role-c").build();
|
||||
|
||||
adminClient.realm(REALM_NAME).roles().create(newRoleComp);
|
||||
}
|
||||
|
||||
/**
|
||||
* see KEYCLOAK-12754
|
||||
*/
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void createNewMixedRealmCompositeRoleWithUnknownClientRoleShouldThrow() {
|
||||
|
||||
String unknownClientRole = "client-role-unknown";
|
||||
RoleRepresentation newRoleComp = RoleBuilder.create().name("role-broken-comp2").composite().realmComposite("role-a").clientComposite(clientId, unknownClientRole).build();
|
||||
adminClient.realm(REALM_NAME).roles().create(newRoleComp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attributes() {
|
||||
for (String id : ids.values()) {
|
||||
|
|
Loading…
Reference in a new issue