diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index aa33be90bdf..bdad7f119fd 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -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 getGroupsStream(RealmModel realm, Stream ids, String search, Integer first, Integer max) { return getGroupDelegate().getGroupsStream(realm, ids, search, first, max); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportDistTest.java index 8d3f3784c17..b59fe274315 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportDistTest.java @@ -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")); + } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/fgap/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/fgap/MgmtPermissions.java index f8fb8805ab7..001676ded27 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/fgap/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/fgap/MgmtPermissions.java @@ -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 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());