fix: adding admin role invalidation when a new realm is found (#46019)

* fix: adding admin role invalidation when a new realm is found

closes: #45966

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* Update model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java

Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>

* adding a comment and a permission tweak for imported realms

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* checking getShouldUseLightweightToken

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>
Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net>
This commit is contained in:
Steven Hawkins 2026-02-13 09:52:52 -05:00 committed by GitHub
parent 74988b5c0a
commit 19118a097c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 81 additions and 1 deletions

View file

@ -26,10 +26,12 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.Config;
import org.keycloak.client.clienttype.ClientTypeManager;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientProvider;
@ -490,6 +492,21 @@ public class RealmCacheSession implements CacheRealmProvider {
if (model == null) {
return null;
}
// to accommodate imports of new realms, check to see if the master admin role is up-to-date
if (!model.getName().equals(Config.getAdminRealm())) {
RealmModel adminRealm = session.realms().getRealmByName(Config.getAdminRealm());
if (adminRealm != null) {
ClientModel clientModel = session.clients().getClientByClientId(adminRealm, model.getName() + "-realm");
if (clientModel != null) {
RoleModel adminRole = adminRealm.getRole(AdminRoles.ADMIN);
if (adminRole.getCompositesStream().noneMatch(r -> (r.isClientRole() && r.getContainerId().equals(clientModel.getId())))) {
registerRoleInvalidation(adminRole.getId(), adminRole.getName(), adminRole.getContainerId());
}
}
}
}
cached = new CachedRealm(loaded, model);
cache.addRevisioned(cached, startupRevision);
adapter = new RealmAdapter(session, cached, this);
@ -1057,6 +1074,7 @@ public class RealmCacheSession implements CacheRealmProvider {
return list.stream().sorted(GroupModel.COMPARE_BY_NAME);
}
@Override
public Stream<GroupModel> getGroupsStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
return getGroupDelegate().getGroupsStream(realm, ids, search, first, max);
}

View file

@ -18,12 +18,15 @@
package org.keycloak.it.cli.dist;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.representations.idm.RealmRepresentation;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -32,6 +35,9 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DistributionTest(defaultOptions = "--db=dev-file")
@RawDistOnly(reason = "Containers are immutable")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -69,4 +75,37 @@ public class ImportDistTest {
cliResult = dist.run("import");
cliResult.assertError("Must specify either --dir or --file options.");
}
@Test
void testImportNewRealm(KeycloakDistribution dist) throws IOException {
File file = new File("target/realm.json");
RealmRepresentation newRealm=new RealmRepresentation();
newRealm.setRealm("anotherRealm");
newRealm.setId("anotherRealm");
newRealm.setEnabled(true);
ObjectMapper mapper = new ObjectMapper();
try (FileOutputStream fos = new FileOutputStream(file)) {
mapper.writeValue(fos, newRealm);
}
var cliResult = dist.run("import", "--file=" + file.getAbsolutePath());
cliResult.assertMessage("Realm 'anotherRealm' imported");
dist.setEnvVar("MY_SECRET", "admin123");
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
CLIResult result = rawDist.run("bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET");
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
rawDist.setManualStop(true);
rawDist.run("start-dev");
CLIResult adminResult = rawDist.kcadm("get", "realms", "--server", "http://localhost:8080", "--realm", "master", "--client", "admin", "--secret", "admin123");
assertEquals(0, adminResult.exitCode());
assertTrue(adminResult.getOutput().contains("anotherRealm"));
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin.fgap;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import jakarta.ws.rs.ForbiddenException;
@ -44,6 +45,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.managers.AuthenticationManager;
@ -165,16 +167,37 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
public boolean hasOneAdminRole(RealmModel realm, String... adminRoles) {
String clientId;
RealmManager realmManager = new RealmManager(session);
boolean masterAdminRealm = false;
if (RealmManager.isAdministrationRealm(adminsRealm)) {
clientId = realm.getMasterAdminClient().getClientId();
masterAdminRealm = true;
} else if (adminsRealm.equals(realm)) {
clientId = realm.getClientByClientId(realmManager.getRealmAdminClientId(realm)).getClientId();
} else {
return false;
}
return identity.hasOneClientRole(clientId, adminRoles);
boolean result = identity.hasOneClientRole(clientId, adminRoles);
if (!result && masterAdminRealm && !adminsRealm.equals(realm)
&& AbstractOIDCProtocolMapper.getShouldUseLightweightToken(session)
&& hasNewAdminRoles(realm, clientId, adminRoles)) {
return true;
}
return result;
}
private boolean hasNewAdminRoles(RealmModel realm, String clientId, String... adminRoles) {
RealmModel masterRealm = getMasterRealm();
UserModel admin = admin();
RoleModel masterAdminRole = masterRealm.getRole(AdminRoles.ADMIN);
if (!admin.hasRole(masterAdminRole)) {
return false;
}
Set<String> roleNames = Set.of(adminRoles);
ClientModel clientModel = masterRealm.getClientByClientId(clientId);
return clientModel != null && masterAdminRole.getCompositesStream()
.anyMatch(r -> (r.isClientRole() && r.getContainerId().equals(clientModel.getId())
&& roleNames.contains(r.getName())));
}
public boolean isAdminSameRealm() {
return auth == null || realm.getId().equals(auth.getRealm().getId());