diff --git a/js/apps/account-ui/src/routes.tsx b/js/apps/account-ui/src/routes.tsx index d4bea05c84..64541c81f2 100644 --- a/js/apps/account-ui/src/routes.tsx +++ b/js/apps/account-ui/src/routes.tsx @@ -59,7 +59,7 @@ export const PersonalInfoRoute: IndexRouteObject = { }; export const RootRoute: RouteObject = { - path: new URL(environment.baseUrl).pathname, + path: decodeURIComponent(new URL(environment.baseUrl).pathname), element: , errorElement: , children: [ diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index ac39288c89..6980bda920 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -19,6 +19,7 @@ package org.keycloak.services.managers; import org.keycloak.Config; import org.keycloak.common.Profile; import org.keycloak.common.enums.SslRequired; +import org.keycloak.common.util.Encode; import org.keycloak.models.AbstractKeycloakTransaction; import org.keycloak.models.AccountRoles; import org.keycloak.models.AdminRoles; @@ -178,7 +179,8 @@ public class RealmManager { adminConsole.setName("${client_" + Constants.ADMIN_CONSOLE_CLIENT_ID + "}"); adminConsole.setRootUrl(Constants.AUTH_ADMIN_URL_PROP); - String baseUrl = "/admin/" + realm.getName() + "/console/"; + + String baseUrl = "/admin/" + Encode.encodePathAsIs(realm.getName()) + "/console/"; adminConsole.setBaseUrl(baseUrl); adminConsole.addRedirectUri(baseUrl + "*"); adminConsole.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); @@ -424,7 +426,8 @@ public class RealmManager { accountClient.setFullScopeAllowed(false); accountClient.setRootUrl(Constants.AUTH_BASE_URL_PROP); - String baseUrl = "/realms/" + realm.getName() + "/account/"; + + String baseUrl = "/realms/" + Encode.encodePathAsIs(realm.getName()) + "/account/"; accountClient.setBaseUrl(baseUrl); accountClient.addRedirectUri(baseUrl + "*"); accountClient.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); @@ -529,6 +532,7 @@ public class RealmManager { try { session.getContext().setRealm(realm); ReservedCharValidator.validate(rep.getRealm()); + ReservedCharValidator.validateLocales(rep.getSupportedLocales()); realm.setName(rep.getRealm()); // setup defaults diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index 39cb26c472..1aff7d9600 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java @@ -23,6 +23,7 @@ import org.keycloak.http.HttpResponse; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotAuthorizedException; import org.keycloak.common.Profile; +import org.keycloak.common.util.Encode; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.models.KeycloakSession; @@ -170,7 +171,7 @@ public class AdminRoot { } catch (JWSInputException e) { throw new NotAuthorizedException("Bearer token format error"); } - String realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1); + String realmName = Encode.decodePath(token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1)); RealmManager realmManager = new RealmManager(session); RealmModel realm = realmManager.getRealmByName(realmName); if (realm == null) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java index 99d0b4a5f9..dbc79946db 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin.realm; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.NotFoundException; @@ -36,6 +37,7 @@ import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory; +import org.keycloak.models.AdminRoles; import org.keycloak.models.CibaConfig; import org.keycloak.models.Constants; import org.keycloak.models.OAuth2DeviceConfig; @@ -67,6 +69,7 @@ import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.events.TestEventsListenerProviderFactory; 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.CredentialBuilder; @@ -86,15 +89,20 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -217,6 +225,57 @@ public class RealmTest extends AbstractAdminTest { Assert.assertNames(adminClient.realms().findAll(), "master", AuthRealm.TEST, REALM_NAME); } + @Test + public void createRealmWithValidConsoleUris() throws Exception { + var realmNameWithSpaces = "new realm"; + + getCleanup() + .addCleanup(() -> adminClient.realms().realm(realmNameWithSpaces).remove()); + + RealmRepresentation rep = new RealmRepresentation(); + rep.setRealm(realmNameWithSpaces); + rep.setEnabled(Boolean.TRUE); + rep.setUsers(Collections.singletonList(UserBuilder.create() + .username("new-realm-admin") + .firstName("new-realm-admin") + .lastName("new-realm-admin") + .email("new-realm-admin@keycloak.org") + .password("password") + .role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN) + .build())); + + adminClient.realms().create(rep); + + Assert.assertNames(adminClient.realms().findAll(), "master", AuthRealm.TEST, REALM_NAME, realmNameWithSpaces); + + final var urlPlaceHolders = ImmutableSet.of("${authBaseUrl}", "${authAdminUrl}"); + + RealmResource newRealm = adminClient.realms().realm(realmNameWithSpaces); + List clientUris = newRealm.clients() + .findAll() + .stream() + .flatMap(client -> Stream.concat(Stream.concat(Stream.concat( + client.getRedirectUris().stream(), + Stream.of(client.getBaseUrl())), + Stream.of(client.getRootUrl())), + Stream.of(client.getAdminUrl()))) + .filter(Objects::nonNull) + .filter(uri -> !urlPlaceHolders.contains(uri)) + .collect(Collectors.toList()); + + assertThat(clientUris, not(empty())); + assertThat(clientUris, everyItem(containsString("/new%20realm/"))); + + try (Keycloak client = AdminClientUtil.createAdminClient(true, realmNameWithSpaces, + "new-realm-admin", "password", Constants.ADMIN_CLI_CLIENT_ID, null)) { + Assert.assertNotNull(client.serverInfo().getInfo()); + } + + adminClient.realms().realm(realmNameWithSpaces).remove(); + + Assert.assertNames(adminClient.realms().findAll(), "master", AuthRealm.TEST, REALM_NAME); + } + @Test public void createRealmRejectReservedCharOrEmptyName() { RealmRepresentation rep = new RealmRepresentation(); @@ -224,6 +283,9 @@ public class RealmTest extends AbstractAdminTest { assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep)); rep.setRealm(""); assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep)); + rep.setRealm("new-realm"); + rep.setId("invalid;id"); + assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep)); } /**