Verify fine-grained admin permissions feature is enabled before checking fine-grained permissions when creating users (#9211)

* Verify fine-grained admin permissions feature is enabled before checking fine-grained permissions when creating users

Co-authored-by: stianst <stianst@gmail.com>

* fixing test

Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Stian Thorgersen 2021-12-17 14:45:56 +01:00 committed by GitHub
parent 31345c49b1
commit 45e9243054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 20 deletions

View file

@ -20,6 +20,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@ -57,10 +58,15 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.models.utils.KeycloakModelUtils.findGroupByPath;
import static org.keycloak.userprofile.UserProfileContext.USER_API;
/**
@ -110,22 +116,8 @@ public class UsersResource {
// first check if user has manage rights
try {
auth.users().requireManage();
}
catch (ForbiddenException exception) {
// if user does not have manage rights, fallback to fine grain admin permissions per group
if (rep.getGroups() != null) {
// if groups is part of the user rep, check if admin has manage_members and manage_membership on each group
for (String groupPath : rep.getGroups()) {
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupPath);
if (group != null) {
auth.groups().requireManageMembers(group);
auth.groups().requireManageMembership(group);
} else {
return ErrorResponse.error(String.format("Group %s not found", groupPath), Response.Status.BAD_REQUEST);
}
}
} else {
// propagate exception if no group specified
} catch (ForbiddenException exception) {
if (!canCreateGroupMembers(rep)) {
throw exception;
}
}
@ -194,6 +186,32 @@ public class UsersResource {
return ErrorResponse.error("Could not create user", Response.Status.BAD_REQUEST);
}
}
private boolean canCreateGroupMembers(UserRepresentation rep) {
if (!Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
return false;
}
List<GroupModel> groups = Optional.ofNullable(rep.getGroups())
.orElse(Collections.emptyList())
.stream().map(path -> findGroupByPath(realm, path))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (groups.isEmpty()) {
return false;
}
// if groups is part of the user rep, check if admin has manage_members and manage_membership on each group
// an exception is thrown in case the current user does not have permissions to manage any of the groups
for (GroupModel group : groups) {
auth.groups().requireManageMembers(group);
auth.groups().requireManageMembership(group);
}
return true;
}
/**
* Get representation of the user
*

View file

@ -40,6 +40,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -58,6 +59,7 @@ import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.auth.page.AuthRealm;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.GroupBuilder;
import org.keycloak.testsuite.utils.tls.TLSUtils;
import javax.ws.rs.ClientErrorException;
@ -65,6 +67,7 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@ -81,6 +84,7 @@ import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)
public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
public static final String CLIENT_NAME = "application";
@ -97,6 +101,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
testRealmRep.setRealm(TEST);
testRealmRep.setEnabled(true);
testRealms.add(testRealmRep);
testRealmRep.setGroups(Arrays.asList(GroupBuilder.create().name("restricted-group").build()));
}
public static void setupDemo(KeycloakSession session) {
@ -656,14 +661,35 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
Assert.assertEquals(403, e.getResponse().getStatus());
}
UserRepresentation newGroupMemberWithAWrongGroup = createUserRepresentation("new-group-member",
UserRepresentation newEmptyGroupList = createUserRepresentation("new-group-member",
"new-group-member@keycloak.org", "New", "Member", true);
newEmptyGroupList.setGroups(Collections.emptyList());
try {
ApiUtil.createUserWithAdminClient(realmClient.realm(TEST), newEmptyGroupList);
Assert.fail("should fail with HTTP response code 403 Forbidden");
} catch (WebApplicationException e) {
Assert.assertEquals(403, e.getResponse().getStatus());
}
UserRepresentation newGroupMemberWithNonExistentGroup = createUserRepresentation("new-group-member",
"new-group-member@keycloak.org", "New", "Member",
Arrays.asList("wrong-group"),true);
try {
ApiUtil.createUserWithAdminClient(realmClient.realm(TEST), newGroupMemberWithAWrongGroup);
Assert.fail("should fail with HTTP response code 400 Bad Request");
ApiUtil.createUserWithAdminClient(realmClient.realm(TEST), newGroupMemberWithNonExistentGroup);
Assert.fail("should fail with HTTP response code 403 Forbidden");
} catch (WebApplicationException e) {
Assert.assertEquals(400, e.getResponse().getStatus());
Assert.assertEquals(403, e.getResponse().getStatus());
}
UserRepresentation newGroupMemberOfNotManagedGroup = createUserRepresentation("new-group-member",
"new-group-member@keycloak.org", "New", "Member",
Arrays.asList("restricted-group"),true);
try {
ApiUtil.createUserWithAdminClient(realmClient.realm(TEST), newGroupMemberOfNotManagedGroup);
Assert.fail("should fail with HTTP response code 403 Forbidden");
} catch (WebApplicationException e) {
Assert.assertEquals(403, e.getResponse().getStatus());
}
UserRepresentation newGroupMember = createUserRepresentation("new-group-member",

View file

@ -26,6 +26,7 @@ import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.TokenVerifier;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
@ -39,6 +40,7 @@ import org.keycloak.common.util.ObjectUtil;
import org.keycloak.credential.CredentialModel;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.PasswordPolicy;
@ -73,6 +75,7 @@ import org.keycloak.testsuite.pages.PageUtils;
import org.keycloak.testsuite.pages.ProceedPage;
import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.GreenMailRule;
@ -90,6 +93,7 @@ import javax.mail.internet.MimeMessage;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
@ -118,6 +122,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.Assert.assertNames;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -2646,4 +2651,42 @@ public class UserTest extends AbstractAdminTest {
group.setId(groupId);
return group;
}
@Test
public void failCreateUserUsingRegularUser() throws Exception {
String regularUserId = ApiUtil.getCreatedId(
testRealm().users().create(UserBuilder.create().username("regular-user").password("password").build()));
UserResource regularUser = testRealm().users().get(regularUserId);
UserRepresentation regularUserRep = regularUser.toRepresentation();
try (Keycloak adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
TEST, regularUserRep.getUsername(), "password", Constants.ADMIN_CLI_CLIENT_ID, null)) {
UserRepresentation invalidUser = UserBuilder.create().username("do-not-create-me").build();
Response response = adminClient.realm(TEST).users().create(invalidUser);
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
invalidUser.setGroups(Collections.emptyList());
response = adminClient.realm(TEST).users().create(invalidUser);
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
}
@Test
public void testCreateUserDoNotGrantRole() {
testRealm().roles().create(RoleBuilder.create().name("realm-role").build());
try {
UserRepresentation userRep = UserBuilder.create().username("alice").password("password").addRoles("realm-role")
.build();
String userId = ApiUtil.getCreatedId(testRealm().users().create(userRep));
UserResource user = testRealm().users().get(userId);
List<RoleRepresentation> realmMappings = user.roles().getAll().getRealmMappings();
assertFalse(realmMappings.stream().map(RoleRepresentation::getName).anyMatch("realm-role"::equals));
} finally {
testRealm().roles().get("realm-role").remove();
}
}
}