mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Include version in system-info for manage-realm and restrict view-system mapping
Closes #45776 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
14fc381eaa
commit
d4e9b16ea9
7 changed files with 144 additions and 57 deletions
|
|
@ -26,7 +26,7 @@ public class SystemInfoRepresentation {
|
|||
private String version;
|
||||
private String serverTime;
|
||||
private String uptime;
|
||||
private long uptimeMillis;
|
||||
private Long uptimeMillis;
|
||||
private String javaVersion;
|
||||
private String javaVendor;
|
||||
private String javaVm;
|
||||
|
|
@ -91,11 +91,11 @@ public class SystemInfoRepresentation {
|
|||
this.uptime = uptime;
|
||||
}
|
||||
|
||||
public long getUptimeMillis() {
|
||||
public Long getUptimeMillis() {
|
||||
return uptimeMillis;
|
||||
}
|
||||
|
||||
public void setUptimeMillis(long uptimeMillis) {
|
||||
public void setUptimeMillis(Long uptimeMillis) {
|
||||
this.uptimeMillis = uptimeMillis;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ They should avoid keeping the returned items in memory by for example binding th
|
|||
|
||||
A similar change was done for the `UserSessionPersisterProvider`.
|
||||
|
||||
== More changes for the `server-info` system information
|
||||
|
||||
In version 26.4.0, the `server-info` endpoint changed to just return the system information for administrators in the admin realm. Nevertheless, the version property was detected to be needed by some products that interact with {project_name}. Now that property is included for administrators in the realm with permission `manage-realm`.
|
||||
|
||||
The workaround of the `view-system` permission is more restricted too. It can only be assigned by administrators in the master realm using link:{adminguide_link}#_fine_grained_permissions[FGAP]. This permission will be deleted in a future version.
|
||||
|
||||
// ------------------------ Deprecated features ------------------------ //
|
||||
== Deprecated features
|
||||
|
||||
|
|
|
|||
|
|
@ -69,5 +69,6 @@ public class AdminRoles {
|
|||
ALL_ROLES.add(ADMIN);
|
||||
ALL_ROLES.add(CREATE_REALM);
|
||||
ALL_ROLES.add(REALM_ADMIN);
|
||||
ALL_ROLES.add(VIEW_SYSTEM);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
@Override
|
||||
public boolean canMapRole(RoleModel role) {
|
||||
if (isRealmAdminRole(role)) {
|
||||
if (AdminRoles.VIEW_SYSTEM.equals(role.getName()) && !root.isAdmin(root.getMasterRealm())) {
|
||||
return false;
|
||||
}
|
||||
if (realm.isAdminPermissionsEnabled()) {
|
||||
// only server or realm admins can map roles if FGAP is enabled
|
||||
return root.isRealmAdmin();
|
||||
|
|
@ -73,6 +76,9 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
@Override
|
||||
public boolean canMapComposite(RoleModel role) {
|
||||
if (isRealmAdminRole(role)) {
|
||||
if (AdminRoles.VIEW_SYSTEM.equals(role.getName()) && !root.isAdmin(root.getMasterRealm())) {
|
||||
return false;
|
||||
}
|
||||
if (realm.isAdminPermissionsEnabled()) {
|
||||
// only server or realm admins can map roles if FGAP is enabled
|
||||
return root.isRealmAdmin();
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ import org.keycloak.representations.info.ThemeInfoRepresentation;
|
|||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.AdminAuth;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissions;
|
||||
import org.keycloak.theme.Theme;
|
||||
|
||||
|
|
@ -121,12 +122,17 @@ public class ServerInfoAdminResource {
|
|||
public ServerInfoRepresentation getInfo() {
|
||||
ServerInfoRepresentation info = new ServerInfoRepresentation();
|
||||
RealmModel userRealm = session.getContext().getRealm();
|
||||
if (RealmManager.isAdministrationRealm(userRealm)
|
||||
|| AdminPermissions.evaluator(session, userRealm, auth).hasOneAdminRole(AdminRoles.VIEW_SYSTEM)) {
|
||||
AdminPermissionEvaluator adminEvaluator = AdminPermissions.evaluator(session, userRealm, auth);
|
||||
if (RealmManager.isAdministrationRealm(userRealm) || adminEvaluator.hasOneAdminRole(AdminRoles.VIEW_SYSTEM)) {
|
||||
// system information is only for admins in the administration realm or fallback view-system role
|
||||
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp(), Version.VERSION));
|
||||
info.setCpuInfo(CpuInfoRepresentation.create());
|
||||
info.setMemoryInfo(MemoryInfoRepresentation.create());
|
||||
} else if (adminEvaluator.realm().canManageRealm()) {
|
||||
// If the user can manage his own realm just add the version information
|
||||
SystemInfoRepresentation systemInfo = new SystemInfoRepresentation();
|
||||
systemInfo.setVersion(Version.VERSION);
|
||||
info.setSystemInfo(systemInfo);
|
||||
}
|
||||
info.setProfileInfo(createProfileInfo());
|
||||
info.setFeatures(createFeatureRepresentations());
|
||||
|
|
|
|||
|
|
@ -17,18 +17,13 @@
|
|||
|
||||
package org.keycloak.tests.admin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
|
@ -47,7 +42,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.info.ServerInfoRepresentation;
|
||||
import org.keycloak.services.resources.admin.AdminAuth.Resource;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
|
|
@ -62,7 +56,6 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
|||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jgroups.util.UUID;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
|
@ -556,51 +549,6 @@ public class PermissionsTest extends AbstractPermissionsTest {
|
|||
invoke(realm -> realm.localization().deleteRealmLocalizationTexts("en"), clients.get("REALM2"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerInfo() throws Exception {
|
||||
// user in master with no permission => forbidden
|
||||
Assert.assertThrows(ForbiddenException.class, () -> clients.get("master-none").serverInfo().getInfo());
|
||||
// user in master with any permission can see the system info
|
||||
ServerInfoRepresentation serverInfo = clients.get("master-view-realm").serverInfo().getInfo();
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNotNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNotNull(serverInfo.getMemoryInfo());
|
||||
|
||||
// user in test realm with no permission => forbidden
|
||||
Assert.assertThrows(ForbiddenException.class, () -> clients.get("none").serverInfo().getInfo());
|
||||
// user in test realm with any permission cannot see the system info
|
||||
serverInfo = clients.get("view-realm").serverInfo().getInfo();
|
||||
Assert.assertNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNull(serverInfo.getMemoryInfo());
|
||||
serverInfo = clients.get("manage-users").serverInfo().getInfo();
|
||||
Assert.assertNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNull(serverInfo.getMemoryInfo());
|
||||
|
||||
// assign the view-system permission to a test realm user and check the fallback works
|
||||
ClientRepresentation realmMgtRep = adminClient.realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
|
||||
ClientResource realmMgtRes = adminClient.realm(REALM_NAME).clients().get(realmMgtRep.getId());
|
||||
RoleRepresentation viewSystem = new RoleRepresentation();
|
||||
viewSystem.setName(AdminRoles.VIEW_SYSTEM);
|
||||
realmMgtRes.roles().create(viewSystem);
|
||||
viewSystem = realmMgtRes.roles().get(AdminRoles.VIEW_SYSTEM).toRepresentation();
|
||||
UserRepresentation userRep = adminClient.realm(REALM_NAME).users().search("view-realm", Boolean.TRUE).get(0);
|
||||
UserResource userRes = adminClient.realm(REALM_NAME).users().get(userRep.getId());
|
||||
userRes.roles().clientLevel(realmMgtRep.getId()).add(Collections.singletonList(viewSystem));
|
||||
try (Keycloak keycloak = adminClientFactory.create().realm(REALM_NAME)
|
||||
.username(userRep.getUsername()).password("password").clientId("test-client")
|
||||
.build()) {
|
||||
serverInfo = keycloak.serverInfo().getInfo();
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNotNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNotNull(serverInfo.getMemoryInfo());
|
||||
} finally {
|
||||
userRes.roles().clientLevel(realmMgtRep.getId()).remove(Collections.singletonList(viewSystem));
|
||||
realmMgtRes.roles().get(AdminRoles.VIEW_SYSTEM).remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyAnyAdminRoleReqired(Invocation invocation) {
|
||||
invoke(invocation, clients.get("view-realm"), true);
|
||||
invoke(invocation, clients.get("manage-realm"), true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2026 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 java.util.Collections;
|
||||
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.info.ServerInfoRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
import org.keycloak.testframework.realm.RealmConfigBuilder;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
@KeycloakIntegrationTest
|
||||
public class ServerInfoPermissionsTest extends AbstractPermissionsTest {
|
||||
|
||||
@InjectRealm(config = PermissionsTestRealm.class, ref = "realm1")
|
||||
ManagedRealm managedRealm1;
|
||||
|
||||
@Test
|
||||
public void testServerInfo() throws Exception {
|
||||
// user in master with no permission => forbidden
|
||||
Assert.assertThrows(ForbiddenException.class, () -> clients.get("master-none").serverInfo().getInfo());
|
||||
// user in master with any permission can see the system info
|
||||
ServerInfoRepresentation serverInfo = clients.get("master-view-realm").serverInfo().getInfo();
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo().getJavaVersion());
|
||||
Assert.assertNotNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNotNull(serverInfo.getMemoryInfo());
|
||||
|
||||
// user in test realm with no permission => forbidden
|
||||
Assert.assertThrows(ForbiddenException.class, () -> clients.get("none").serverInfo().getInfo());
|
||||
// user in test realm with any permission cannot see the system info
|
||||
serverInfo = clients.get("view-realm").serverInfo().getInfo();
|
||||
Assert.assertNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNull(serverInfo.getMemoryInfo());
|
||||
serverInfo = clients.get("manage-users").serverInfo().getInfo();
|
||||
Assert.assertNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNull(serverInfo.getMemoryInfo());
|
||||
// user with manage realm can only see the version
|
||||
serverInfo = clients.get("manage-realm").serverInfo().getInfo();
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo().getVersion());
|
||||
Assert.assertNull(serverInfo.getSystemInfo().getJavaVersion());
|
||||
Assert.assertNull(serverInfo.getSystemInfo().getOsName());
|
||||
Assert.assertNull(serverInfo.getSystemInfo().getServerTime());
|
||||
Assert.assertNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNull(serverInfo.getMemoryInfo());
|
||||
|
||||
// assign the view-system permission to a test realm user and check the fallback works
|
||||
ClientRepresentation realmMgtRep = adminClient.realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
|
||||
ClientResource realmMgtRes = adminClient.realm(REALM_NAME).clients().get(realmMgtRep.getId());
|
||||
RoleRepresentation createViewSystem = new RoleRepresentation();
|
||||
createViewSystem.setName(AdminRoles.VIEW_SYSTEM);
|
||||
realmMgtRes.roles().create(createViewSystem);
|
||||
final RoleRepresentation viewSystem = realmMgtRes.roles().get(AdminRoles.VIEW_SYSTEM).toRepresentation();
|
||||
UserRepresentation userRep = adminClient.realm(REALM_NAME).users().search("view-realm", Boolean.TRUE).get(0);
|
||||
// view-system cannot be assigned by admin in the permissions realm
|
||||
Assert.assertThrows(ForbiddenException.class, () -> clients.get("realm-admin")
|
||||
.realm(REALM_NAME).users().get(userRep.getId()).roles().clientLevel(realmMgtRep.getId())
|
||||
.add(Collections.singletonList(viewSystem)));
|
||||
// view-system can be assigned by a master realm-admin using FGAP
|
||||
UserResource userRes = adminClient.realm(REALM_NAME).users().get(userRep.getId());
|
||||
userRes.roles().clientLevel(realmMgtRep.getId())
|
||||
.add(Collections.singletonList(viewSystem));
|
||||
try (Keycloak keycloak = adminClientFactory.create().realm(REALM_NAME)
|
||||
.username(userRep.getUsername()).password("password").clientId("test-client")
|
||||
.build()) {
|
||||
serverInfo = keycloak.serverInfo().getInfo();
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo());
|
||||
Assert.assertNotNull(serverInfo.getSystemInfo().getJavaVersion());
|
||||
Assert.assertNotNull(serverInfo.getCpuInfo());
|
||||
Assert.assertNotNull(serverInfo.getMemoryInfo());
|
||||
} finally {
|
||||
userRes.roles().clientLevel(realmMgtRep.getId()).remove(Collections.singletonList(viewSystem));
|
||||
realmMgtRes.roles().get(AdminRoles.VIEW_SYSTEM).remove();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PermissionsTestRealm extends PermissionsTestRealmConfig1 {
|
||||
|
||||
@Override
|
||||
public RealmConfigBuilder configure(RealmConfigBuilder realm) {
|
||||
// configure with permissions enable to test view-system assignment
|
||||
return super.configure(realm).adminPermissionsEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue