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));
}
/**