Use MgmtPermissionsV2 by default

Closes #40192

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik
2025-07-01 16:22:12 +02:00
committed by Pedro Igor
parent eba4934950
commit 114afee7f1
10 changed files with 146 additions and 98 deletions

View File

@@ -18,6 +18,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.jboss.logging.Logger;
import org.keycloak.admin.ui.rest.model.BruteUser;
import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@@ -148,7 +149,7 @@ public class BruteForceUsersResource {
private Stream<BruteUser> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
attributes.put(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts.toString());
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
if (!groupIds.isEmpty()) {
session.setAttribute(UserModel.GROUPS, groupIds);

View File

@@ -442,10 +442,10 @@ public class UsersResource {
if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(realm, parameters);
} else {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
return session.users().getUsersCount(realm, parameters);
} else {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
return session.users().getUsersCount(realm, parameters, auth.groups().getGroupIdsWithViewPermission());
} else {
return session.users().getUsersCount(realm, parameters);
}
}
} else if (last != null || first != null || email != null || username != null || emailVerified != null || enabled != null || !searchAttributes.isEmpty()) {
@@ -482,19 +482,20 @@ public class UsersResource {
if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(realm, parameters);
} else {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
return session.users().getUsersCount(realm, parameters);
} else {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
return session.users().getUsersCount(realm, parameters, auth.groups().getGroupIdsWithViewPermission());
} else {
return session.users().getUsersCount(realm, parameters);
}
}
} else if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(realm);
} else {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
return session.users().getUsersCount(realm, auth.groups().getGroupIdsWithViewPermission());
} else {
return session.users().getUsersCount(realm);
}
return session.users().getUsersCount(realm, auth.groups().getGroupIdsWithViewPermission());
}
}
@@ -512,7 +513,7 @@ public class UsersResource {
private Stream<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
attributes.put(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts.toString());
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
if (!groupIds.isEmpty()) {
session.setAttribute(UserModel.GROUPS, groupIds);

View File

@@ -16,7 +16,6 @@
*/
package org.keycloak.services.resources.admin.fgap;
import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.common.Profile;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
@@ -36,28 +35,27 @@ public class AdminPermissions {
public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, AdminAuth auth) {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
return new MgmtPermissionsV2(session, realm, auth);
}
return new MgmtPermissions(session, realm, auth);
}
public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, RealmModel adminsRealm, UserModel admin) {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
return new MgmtPermissionsV2(session, adminsRealm, admin);
}
return new MgmtPermissions(session, realm, adminsRealm, admin);
}
public static RealmsPermissionEvaluator realms(KeycloakSession session, AdminAuth auth) {
RealmModel realm = session.getContext().getRealm();
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
return new MgmtPermissionsV2(session, auth);
}
return new MgmtPermissions(session, auth);
}
public static AdminPermissionManagement management(KeycloakSession session, RealmModel realm) {
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
return new MgmtPermissionsV2(session, realm);
}
return new MgmtPermissions(session, realm);

View File

@@ -21,7 +21,6 @@ import jakarta.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.jgroups.util.UUID;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
@@ -41,11 +40,6 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.resources.admin.AdminAuth.Resource;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@@ -58,14 +52,11 @@ import org.keycloak.testsuite.util.FederatedIdentityBuilder;
import org.keycloak.testsuite.util.IdentityProviderBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.services.resources.admin.AdminAuth.Resource.AUTHORIZATION;
import static org.keycloak.services.resources.admin.AdminAuth.Resource.CLIENT;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -297,75 +288,6 @@ public class PermissionsTest extends AbstractPermissionsTest {
invoke(realm -> realm.clientInitialAccess().delete("nosuch"), Resource.CLIENT, true);
}
@Test
public void clientAuthorization() {
String fooAuthzClientUuid = ApiUtil.getCreatedId(managedRealm1.admin().clients().create(ClientConfigBuilder.create().clientId("foo-authz").build()));
ClientRepresentation foo = managedRealm1.admin().clients().get(fooAuthzClientUuid).toRepresentation();
invoke((realm, response) -> {
foo.setServiceAccountsEnabled(true);
foo.setAuthorizationServicesEnabled(true);
realm.clients().get(foo.getId()).update(foo);
}, CLIENT, true);
invoke(realm -> realm.clients().get(foo.getId()).authorization().getSettings(), AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
ResourceServerRepresentation settings = authorization.getSettings();
authorization.update(settings);
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resources();
}, AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scopes();
}, AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policies();
}, AUTHORIZATION, false);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet())));
}, AUTHORIZATION, true);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.scopes().create(new ScopeRepresentation("Test")));
}, AUTHORIZATION, true);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
representation.setName("Test PermissionsTest");
representation.addResource("Default Resource");
response.set(authorization.permissions().resource().create(representation));
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").update(new ResourceRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").update(new ScopeRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").update(new PolicyRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").remove();
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").remove();
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").remove();
}, AUTHORIZATION, true);
}
@Test
public void roles() {
RoleRepresentation newRole = RoleBuilder.create().name("sample-role").build();
@@ -544,9 +466,6 @@ public class PermissionsTest extends AbstractPermissionsTest {
invoke(realm -> realm.users().get(user.getId()).update(user), clients.get(AdminRoles.QUERY_CLIENTS), false);
// users with query-user role should be able to query required actions so the user detail page can be rendered successfully when fine-grained permissions are enabled.
invoke(realm -> realm.flows().getRequiredActions(), clients.get(AdminRoles.QUERY_USERS), true);
// users with query-user role should be able to query clients so the user detail page can be rendered successfully when fine-grained permissions are enabled.
// if the admin wants to restrict the clients that an user can see he can define permissions for these clients
invoke(realm -> clients.get(AdminRoles.QUERY_USERS).realm(REALM_NAME).clients().findAll(), clients.get(AdminRoles.QUERY_USERS), true);
invoke(realm -> clients.get(AdminRoles.VIEW_USERS).realm(REALM_NAME).users().get(user.getId()).getConfiguredUserStorageCredentialTypes(),
clients.get(AdminRoles.VIEW_USERS), true);
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.tests.admin;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ClientConfigBuilder;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.tests.admin.authz.fgap.KeycloakAdminPermissionsV1ServerConfig;
import org.keycloak.tests.utils.admin.ApiUtil;
import java.util.Collections;
import static org.keycloak.services.resources.admin.AdminAuth.Resource.AUTHORIZATION;
import static org.keycloak.services.resources.admin.AdminAuth.Resource.CLIENT;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsV1ServerConfig.class)
public class PermissionsTestV1 extends AbstractPermissionsTest {
@InjectRealm(config = PermissionsTestRealmConfig1.class, ref = "realm1")
ManagedRealm managedRealm1;
@InjectRealm(config = PermissionsTestRealmConfig2.class, ref = "realm2")
ManagedRealm managedRealm2;
@Test
public void clientAuthorization() {
String fooAuthzClientUuid = ApiUtil.getCreatedId(managedRealm1.admin().clients().create(ClientConfigBuilder.create().clientId("foo-authz").build()));
ClientRepresentation foo = managedRealm1.admin().clients().get(fooAuthzClientUuid).toRepresentation();
invoke((realm, response) -> {
foo.setServiceAccountsEnabled(true);
foo.setAuthorizationServicesEnabled(true);
realm.clients().get(foo.getId()).update(foo);
}, CLIENT, true);
invoke(realm -> realm.clients().get(foo.getId()).authorization().getSettings(), AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
ResourceServerRepresentation settings = authorization.getSettings();
authorization.update(settings);
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resources();
}, AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scopes();
}, AUTHORIZATION, false);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policies();
}, AUTHORIZATION, false);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet())));
}, AUTHORIZATION, true);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.scopes().create(new ScopeRepresentation("Test")));
}, AUTHORIZATION, true);
invoke((realm, response) -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
representation.setName("Test PermissionsTest");
representation.addResource("Default Resource");
response.set(authorization.permissions().resource().create(representation));
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").update(new ResourceRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").update(new ScopeRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").update(new PolicyRepresentation());
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").remove();
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").remove();
}, AUTHORIZATION, true);
invoke(realm -> {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").remove();
}, AUTHORIZATION, true);
}
}

View File

@@ -42,12 +42,15 @@ import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import org.keycloak.common.Profile;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)
public class IllegalAdminUpgradeTest extends AbstractKeycloakTest {
public static final String CLIENT_NAME = "application";

View File

@@ -57,6 +57,7 @@ import org.keycloak.services.resources.admin.fgap.AdminPermissions;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.util.AdminClientUtil;
@@ -90,6 +91,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
*/
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
@EnableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipRestart = true)
@DisableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2, skipRestart = true)
public class ClientTokenExchangeSAML2Test extends AbstractKeycloakTest {
private static final String SAML_SIGNED_TARGET = "http://localhost:8080/saml-signed-assertion/";

View File

@@ -35,6 +35,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
@@ -43,7 +44,6 @@ import jakarta.ws.rs.core.Response;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -58,6 +58,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
*/
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
@EnableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipRestart = true)
@DisableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2, skipRestart = true)
public class StandardTokenExchangeV1Test extends AbstractKeycloakTest {
@Rule

View File

@@ -23,6 +23,7 @@ import jakarta.ws.rs.core.Response;
import org.junit.Test;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.Profile;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
@@ -36,6 +37,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
*/
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
@EnableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipRestart = true)
@DisableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2, skipRestart = true)
public class StandardTokenExchangeV2WithLegacyTokenExchangeTest extends StandardTokenExchangeV2Test {
@Test

View File

@@ -63,6 +63,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
*/
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
@EnableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipRestart = true)
@DisableFeature(value = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2, skipRestart = true)
public class SubjectImpersonationTokenExchangeV1Test extends AbstractKeycloakTest {
@Rule