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 = {
path: new URL(environment.baseUrl).pathname,
path: decodeURIComponent(new URL(environment.baseUrl).pathname),
element: <Root />,
errorElement: <ErrorPage />,
children: [

View file

@ -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

View file

@ -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) {

View file

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