Encode realm name in console URIs (#29102)

Before this fix console uris (including the client redirect uris) did not contain the url encoded realm name and therefore were invalid.

closes #25807

Signed-off-by: Philip Sanetra <code@psanetra.de>
Signed-off-by: rmartinc <rmartinc@redhat.com>


Co-authored-by: Philip Sanetra <code@psanetra.de>
Co-authored-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
Ricardo Martin 2024-05-02 10:30:06 +02:00 committed by GitHub
parent 879fe082dd
commit 65bdf1a604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 4 deletions

View file

@ -59,7 +59,7 @@ export const PersonalInfoRoute: IndexRouteObject = {
}; };
export const RootRoute: RouteObject = { export const RootRoute: RouteObject = {
path: new URL(environment.baseUrl).pathname, path: decodeURIComponent(new URL(environment.baseUrl).pathname),
element: <Root />, element: <Root />,
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [

View file

@ -19,6 +19,7 @@ package org.keycloak.services.managers;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.Encode;
import org.keycloak.models.AbstractKeycloakTransaction; import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.AccountRoles; import org.keycloak.models.AccountRoles;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
@ -178,7 +179,8 @@ public class RealmManager {
adminConsole.setName("${client_" + Constants.ADMIN_CONSOLE_CLIENT_ID + "}"); adminConsole.setName("${client_" + Constants.ADMIN_CONSOLE_CLIENT_ID + "}");
adminConsole.setRootUrl(Constants.AUTH_ADMIN_URL_PROP); 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.setBaseUrl(baseUrl);
adminConsole.addRedirectUri(baseUrl + "*"); adminConsole.addRedirectUri(baseUrl + "*");
adminConsole.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); adminConsole.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
@ -424,7 +426,8 @@ public class RealmManager {
accountClient.setFullScopeAllowed(false); accountClient.setFullScopeAllowed(false);
accountClient.setRootUrl(Constants.AUTH_BASE_URL_PROP); 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.setBaseUrl(baseUrl);
accountClient.addRedirectUri(baseUrl + "*"); accountClient.addRedirectUri(baseUrl + "*");
accountClient.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); accountClient.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
@ -529,6 +532,7 @@ public class RealmManager {
try { try {
session.getContext().setRealm(realm); session.getContext().setRealm(realm);
ReservedCharValidator.validate(rep.getRealm()); ReservedCharValidator.validate(rep.getRealm());
ReservedCharValidator.validateLocales(rep.getSupportedLocales());
realm.setName(rep.getRealm()); realm.setName(rep.getRealm());
// setup defaults // setup defaults

View file

@ -23,6 +23,7 @@ import org.keycloak.http.HttpResponse;
import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.NotAuthorizedException;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.util.Encode;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -170,7 +171,7 @@ public class AdminRoot {
} catch (JWSInputException e) { } catch (JWSInputException e) {
throw new NotAuthorizedException("Bearer token format error"); 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); RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName); RealmModel realm = realmManager.getRealmByName(realmName);
if (realm == null) { if (realm == null) {

View file

@ -17,6 +17,7 @@
package org.keycloak.testsuite.admin.realm; package org.keycloak.testsuite.admin.realm;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException; 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.OperationType;
import org.keycloak.events.admin.ResourceType; import org.keycloak.events.admin.ResourceType;
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory; import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.CibaConfig; import org.keycloak.models.CibaConfig;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.OAuth2DeviceConfig; 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.events.TestEventsListenerProviderFactory;
import org.keycloak.testsuite.runonserver.RunHelpers; import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.updaters.Creator; import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.CredentialBuilder; import org.keycloak.testsuite.util.CredentialBuilder;
@ -86,15 +89,20 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; 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); 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<String> 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 @Test
public void createRealmRejectReservedCharOrEmptyName() { public void createRealmRejectReservedCharOrEmptyName() {
RealmRepresentation rep = new RealmRepresentation(); RealmRepresentation rep = new RealmRepresentation();
@ -224,6 +283,9 @@ public class RealmTest extends AbstractAdminTest {
assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep)); assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep));
rep.setRealm(""); rep.setRealm("");
assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep)); assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep));
rep.setRealm("new-realm");
rep.setId("invalid;id");
assertThrows(BadRequestException.class, () -> adminClient.realms().create(rep));
} }
/** /**