From f99299ee3956d138c001769b0fe4bcfcc90a219f Mon Sep 17 00:00:00 2001 From: stianst Date: Mon, 30 Jul 2018 22:38:43 +0200 Subject: [PATCH] KEYCLOAK-7967 Introduce Hostname SPI --- .../content/bin/migrate-domain-clustered.cli | 8 + .../content/bin/migrate-domain-standalone.cli | 8 + .../content/bin/migrate-standalone-ha.cli | 8 + .../content/bin/migrate-standalone.cli | 8 + .../org/keycloak/models/KeycloakContext.java | 2 +- .../org/keycloak/models/KeycloakUriInfo.java | 161 +++++++++++++++++ .../org/keycloak/urls/HostnameProvider.java | 41 +++++ .../urls/HostnameProviderFactory.java | 42 +++++ .../java/org/keycloak/urls/HostnameSpi.java | 45 +++++ .../services/org.keycloak.provider.Spi | 3 +- .../services/DefaultKeycloakContext.java | 11 +- .../ClientRegistrationAuth.java | 3 +- .../ClientRegistrationTokenUtils.java | 23 +-- .../admin/ClientInitialAccessResource.java | 2 +- .../resources/admin/ClientResource.java | 2 +- .../keycloak/url/FixedHostnameProvider.java | 40 +++++ .../url/FixedHostnameProviderFactory.java | 35 ++++ .../keycloak/url/RequestHostnameProvider.java | 19 ++ .../url/RequestHostnameProviderFactory.java | 21 +++ .../org.keycloak.urls.HostnameProviderFactory | 2 + .../keycloak/testsuite/util/OAuthClient.java | 17 +- .../testsuite/AbstractKeycloakTest.java | 14 +- .../testsuite/url/FixedHostnameTest.java | 165 ++++++++++++++++++ .../resources/META-INF/keycloak-server.json | 13 +- .../resources/META-INF/keycloak-server.json | 14 +- .../messages/admin-messages_en.properties | 2 + .../resources/partials/realm-detail.html | 8 + travis-run-tests.sh | 2 +- .../default-server-subsys-config.properties | 10 ++ .../cli/default-keycloak-subsys-config.cli | 2 + 30 files changed, 704 insertions(+), 27 deletions(-) create mode 100644 server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java create mode 100644 server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/urls/HostnameProviderFactory.java create mode 100644 server-spi/src/main/java/org/keycloak/urls/HostnameSpi.java create mode 100644 services/src/main/java/org/keycloak/url/FixedHostnameProvider.java create mode 100644 services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/url/RequestHostnameProvider.java create mode 100644 services/src/main/java/org/keycloak/url/RequestHostnameProviderFactory.java create mode 100644 services/src/main/resources/META-INF/services/org.keycloak.urls.HostnameProviderFactory create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli index 3498794b36..d75dd70004 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli @@ -439,4 +439,12 @@ if (outcome == failed) of /profile=$clusteredProfile/subsystem=keycloak-server/s echo end-if +# Migrate from 4.2.0 to 4.3.0 +if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/:read-resource + echo Adding spi=hostname... + /subsystem=keycloak-server/spi=hostname/:add(default-provider=request) + /subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) + echo +end-if + echo *** End Migration of /profile=$clusteredProfile *** \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli index 33a233430f..d0af97abf1 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli @@ -396,4 +396,12 @@ if (outcome == failed) of /profile=$standaloneProfile/subsystem=keycloak-server/ echo end-if +# Migrate from 4.2.0 to 4.3.0 +if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/:read-resource + echo Adding spi=hostname... + /subsystem=keycloak-server/spi=hostname/:add(default-provider=request) + /subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) + echo +end-if + echo *** End Migration of /profile=$standaloneProfile *** \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli index aea4a6f650..5b11647bdc 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli @@ -423,4 +423,12 @@ if (outcome == failed) of /subsystem=keycloak-server/spi=x509cert-lookup/:read-r echo end-if +# Migrate from 4.2.0 to 4.3.0 +if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/:read-resource + echo Adding spi=hostname... + /subsystem=keycloak-server/spi=hostname/:add(default-provider=request) + /subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) + echo +end-if + echo *** End Migration *** \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli index 9bf0b420a4..5194c450c9 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli @@ -367,4 +367,12 @@ if (outcome == failed) of /subsystem=keycloak-server/spi=x509cert-lookup/:read-r echo end-if +# Migrate from 4.2.0 to 4.3.0 +if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/:read-resource + echo Adding spi=hostname... + /subsystem=keycloak-server/spi=hostname/:add(default-provider=request) + /subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) + echo +end-if + echo *** End Migration *** \ No newline at end of file diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java index be5f55189f..44071490bd 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java @@ -33,7 +33,7 @@ public interface KeycloakContext { String getContextPath(); - UriInfo getUri(); + KeycloakUriInfo getUri(); HttpHeaders getRequestHeaders(); diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java b/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java new file mode 100644 index 0000000000..2643cc0c11 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models; + +import org.jboss.resteasy.specimpl.ResteasyUriBuilder; +import org.keycloak.models.KeycloakSession; +import org.keycloak.urls.HostnameProvider; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.PathSegment; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.util.List; + +public class KeycloakUriInfo implements UriInfo { + + private final UriInfo delegate; + private final String hostname; + private final int port; + + private URI absolutePath; + private URI requestURI; + private URI baseURI; + + public KeycloakUriInfo(KeycloakSession session, UriInfo delegate) { + this.delegate = delegate; + + HostnameProvider hostnameProvider = session.getProvider(HostnameProvider.class); + this.hostname = hostnameProvider.getHostname(delegate); + this.port = hostnameProvider.getPort(delegate); + } + + public UriInfo getDelegate() { + return delegate; + } + + @Override + public URI getRequestUri() { + if (requestURI == null) { + requestURI = delegate.getRequestUriBuilder().host(hostname).port(port).build(); + } + return requestURI; + } + + @Override + public UriBuilder getRequestUriBuilder() { + return UriBuilder.fromUri(getRequestUri()); + } + + @Override + public URI getAbsolutePath() { + if (absolutePath == null) { + absolutePath = delegate.getAbsolutePathBuilder().host(hostname).port(port).build(); + } + return absolutePath; + } + + @Override + public UriBuilder getAbsolutePathBuilder() { + return UriBuilder.fromUri(getAbsolutePath()); + } + + @Override + public URI getBaseUri() { + if (baseURI == null) { + baseURI = delegate.getBaseUriBuilder().host(hostname).port(port).build(); + } + return baseURI; + } + + @Override + public UriBuilder getBaseUriBuilder() { + return UriBuilder.fromUri(getBaseUri()); + } + + @Override + public URI resolve(URI uri) { + return getBaseUri().resolve(uri); + } + + @Override + public URI relativize(URI uri) { + URI from = this.getRequestUri(); + URI to = uri; + if (uri.getScheme() == null && uri.getHost() == null) { + to = this.getBaseUriBuilder().replaceQuery(null).path(uri.getPath()).replaceQuery(uri.getQuery()).fragment(uri.getFragment()).build(new Object[0]); + } + + return ResteasyUriBuilder.relativize(from, to); + } + + @Override + public String getPath() { + return delegate.getPath(); + } + + @Override + public String getPath(boolean decode) { + return delegate.getPath(decode); + } + + @Override + public List getPathSegments() { + return delegate.getPathSegments(); + } + + @Override + public List getPathSegments(boolean decode) { + return delegate.getPathSegments(decode); + } + + @Override + public MultivaluedMap getPathParameters() { + return delegate.getPathParameters(); + } + + @Override + public MultivaluedMap getPathParameters(boolean decode) { + return delegate.getPathParameters(decode); + } + + @Override + public MultivaluedMap getQueryParameters() { + return delegate.getQueryParameters(); + } + + @Override + public MultivaluedMap getQueryParameters(boolean decode) { + return delegate.getQueryParameters(decode); + } + + @Override + public List getMatchedURIs() { + return delegate.getMatchedURIs(); + } + + @Override + public List getMatchedURIs(boolean decode) { + return delegate.getMatchedURIs(decode); + } + + @Override + public List getMatchedResources() { + return delegate.getMatchedResources(); + } +} diff --git a/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java b/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java new file mode 100644 index 0000000000..0d33b93ab3 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.urls; + +import org.keycloak.models.KeycloakContext; +import org.keycloak.provider.Provider; + +import javax.ws.rs.core.UriInfo; + +public interface HostnameProvider extends Provider { + + /** + * Return the hostname. Http headers, realm details, etc. can be retrieved from the KeycloakSession. Do NOT use + * {@link KeycloakContext#getUri()} as it will in turn call the HostnameProvider resulting in an infinite loop! + * + * @param originalUriInfo the original UriInfo before hostname is replaced by the HostnameProvider + * @return the hostname + */ + String getHostname(UriInfo originalUriInfo); + + int getPort(UriInfo originalUriInfo); + + @Override + default void close() { + } + +} diff --git a/server-spi/src/main/java/org/keycloak/urls/HostnameProviderFactory.java b/server-spi/src/main/java/org/keycloak/urls/HostnameProviderFactory.java new file mode 100644 index 0000000000..73b4a2d6f2 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/urls/HostnameProviderFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.urls; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderFactory; + +public interface HostnameProviderFactory extends ProviderFactory { + + @Override + default void close() { + } + + @Override + default void init(Config.Scope config) { + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + } + + @Override + default int order() { + return 0; + } + +} diff --git a/server-spi/src/main/java/org/keycloak/urls/HostnameSpi.java b/server-spi/src/main/java/org/keycloak/urls/HostnameSpi.java new file mode 100644 index 0000000000..624fac2fb7 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/urls/HostnameSpi.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.urls; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class HostnameSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "hostname"; + } + + @Override + public Class getProviderClass() { + return HostnameProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return HostnameProviderFactory.class; + } + +} diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 86539b5e99..8fb53ab89e 100755 --- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -34,4 +34,5 @@ org.keycloak.storage.UserStorageProviderSpi org.keycloak.theme.ThemeResourceSpi -org.keycloak.theme.ThemeSelectorSpi \ No newline at end of file +org.keycloak.theme.ThemeSelectorSpi +org.keycloak.urls.HostnameSpi \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index e1e0880fca..4f482effb7 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -22,6 +22,7 @@ import org.keycloak.common.ClientConnection; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakUriInfo; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.services.resources.KeycloakApplication; @@ -45,6 +46,8 @@ public class DefaultKeycloakContext implements KeycloakContext { private KeycloakSession session; + private KeycloakUriInfo uriInfo; + public DefaultKeycloakContext(KeycloakSession session) { this.session = session; } @@ -64,8 +67,11 @@ public class DefaultKeycloakContext implements KeycloakContext { } @Override - public UriInfo getUri() { - return getContextObject(UriInfo.class); + public KeycloakUriInfo getUri() { + if (uriInfo == null) { + uriInfo = new KeycloakUriInfo(session, getContextObject(UriInfo.class)); + } + return uriInfo; } @Override @@ -86,6 +92,7 @@ public class DefaultKeycloakContext implements KeycloakContext { @Override public void setRealm(RealmModel realm) { this.realm = realm; + this.uriInfo = null; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java index 88986b5448..4e1a96f2dd 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java @@ -71,7 +71,6 @@ public class ClientRegistrationAuth { private void init() { realm = session.getContext().getRealm(); - UriInfo uri = session.getContext().getUri(); String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (authorizationHeader == null) { @@ -85,7 +84,7 @@ public class ClientRegistrationAuth { token = split[1]; - ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, uri, token); + ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, token); if (tokenVerification.getError() != null) { throw unauthorized(tokenVerification.getError().getMessage()); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java index 270ca2abe0..111bd0d61d 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java @@ -31,6 +31,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.JsonWebToken; import org.keycloak.services.Urls; import org.keycloak.services.clientregistration.policy.RegistrationAuth; +import org.keycloak.urls.HostnameProvider; import org.keycloak.util.TokenUtil; import javax.ws.rs.core.UriInfo; @@ -66,25 +67,25 @@ public class ClientRegistrationTokenUtils { } public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client, RegistrationAuth registrationAuth) { - return updateRegistrationAccessToken(session, session.getContext().getRealm(), session.getContext().getUri(), client, registrationAuth); + return updateRegistrationAccessToken(session, session.getContext().getRealm(), client, registrationAuth); } - public static String updateRegistrationAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientModel client, RegistrationAuth registrationAuth) { + public static String updateRegistrationAccessToken(KeycloakSession session, RealmModel realm, ClientModel client, RegistrationAuth registrationAuth) { String id = KeycloakModelUtils.generateId(); client.setRegistrationToken(id); RegistrationAccessToken regToken = new RegistrationAccessToken(); regToken.setRegistrationAuth(registrationAuth.toString().toLowerCase()); - return setupToken(regToken, session, realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0); + return setupToken(regToken, session, realm, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0); } - public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientInitialAccessModel model) { + public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, ClientInitialAccessModel model) { JsonWebToken initialToken = new JsonWebToken(); - return setupToken(initialToken, session, realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0); + return setupToken(initialToken, session, realm, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0); } - public static TokenVerification verifyToken(KeycloakSession session, RealmModel realm, UriInfo uri, String token) { + public static TokenVerification verifyToken(KeycloakSession session, RealmModel realm, String token) { if (token == null) { return TokenVerification.error(new RuntimeException("Missing token")); } @@ -110,7 +111,7 @@ public class ClientRegistrationTokenUtils { return TokenVerification.error(new RuntimeException("Token is not JWT", e)); } - if (!getIssuer(realm, uri).equals(jwt.getIssuer())) { + if (!getIssuer(session, realm).equals(jwt.getIssuer())) { return TokenVerification.error(new RuntimeException("Issuer from token don't match with the realm issuer.")); } @@ -127,8 +128,8 @@ public class ClientRegistrationTokenUtils { return TokenVerification.success(kid, jwt); } - private static String setupToken(JsonWebToken jwt, KeycloakSession session, RealmModel realm, UriInfo uri, String id, String type, int expiration) { - String issuer = getIssuer(realm, uri); + private static String setupToken(JsonWebToken jwt, KeycloakSession session, RealmModel realm, String id, String type, int expiration) { + String issuer = getIssuer(session, realm); jwt.type(type); jwt.id(id); @@ -143,8 +144,8 @@ public class ClientRegistrationTokenUtils { return token; } - private static String getIssuer(RealmModel realm, UriInfo uri) { - return Urls.realmIssuer(uri.getBaseUri(), realm.getName()); + private static String getIssuer(KeycloakSession session, RealmModel realm) { + return Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()); } protected static class TokenVerification { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java index f6ddd11681..8d0fae36b6 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java @@ -83,7 +83,7 @@ public class ClientInitialAccessResource { ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel); - String token = ClientRegistrationTokenUtils.createInitialAccessToken(session, realm, session.getContext().getUri(), clientInitialAccessModel); + String token = ClientRegistrationTokenUtils.createInitialAccessToken(session, realm, clientInitialAccessModel); rep.setToken(token); response.setStatus(Response.Status.CREATED.getStatusCode()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index a78a25545b..1baf71066b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -251,7 +251,7 @@ public class ClientResource { public ClientRepresentation regenerateRegistrationAccessToken() { auth.clients().requireManage(client); - String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, realm, session.getContext().getUri(), client, RegistrationAuth.AUTHENTICATED); + String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, realm, client, RegistrationAuth.AUTHENTICATED); ClientRepresentation rep = ModelToRepresentation.toRepresentation(client); rep.setRegistrationAccessToken(token); diff --git a/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java b/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java new file mode 100644 index 0000000000..d871270d62 --- /dev/null +++ b/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java @@ -0,0 +1,40 @@ +package org.keycloak.url; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.urls.HostnameProvider; + +import javax.ws.rs.core.UriInfo; + +public class FixedHostnameProvider implements HostnameProvider { + + private final KeycloakSession session; + private final String globalHostname; + private final int httpPort; + private final int httpsPort; + + public FixedHostnameProvider(KeycloakSession session, String globalHostname, int httpPort, int httpsPort) { + this.session = session; + this.globalHostname = globalHostname; + this.httpPort = httpPort; + this.httpsPort = httpsPort; + } + + @Override + public String getHostname(UriInfo originalUriInfo) { + RealmModel realm = session.getContext().getRealm(); + if (realm != null) { + String realmHostname = session.getContext().getRealm().getAttribute("hostname"); + if (realmHostname != null && !realmHostname.isEmpty()) { + return realmHostname; + } + } + return this.globalHostname; + } + + @Override + public int getPort(UriInfo originalUriInfo) { + return originalUriInfo.getRequestUri().getScheme().equals("https") ? httpsPort : httpPort; + } + +} diff --git a/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java b/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java new file mode 100644 index 0000000000..1899cdadad --- /dev/null +++ b/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java @@ -0,0 +1,35 @@ +package org.keycloak.url; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.urls.HostnameProvider; +import org.keycloak.urls.HostnameProviderFactory; + +public class FixedHostnameProviderFactory implements HostnameProviderFactory { + + private String hostname; + private int httpPort; + private int httpsPort; + + @Override + public HostnameProvider create(KeycloakSession session) { + return new FixedHostnameProvider(session, hostname, httpPort, httpsPort); + } + + @Override + public void init(Config.Scope config) { + this.hostname = config.get("hostname"); + if (this.hostname == null) { + throw new RuntimeException("hostname not set"); + } + + this.httpPort = config.getInt("httpPort", -1); + this.httpsPort = config.getInt("httpsPort", -1); + } + + @Override + public String getId() { + return "fixed"; + } + +} diff --git a/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java b/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java new file mode 100644 index 0000000000..f5a6f00c2f --- /dev/null +++ b/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java @@ -0,0 +1,19 @@ +package org.keycloak.url; + +import org.keycloak.urls.HostnameProvider; + +import javax.ws.rs.core.UriInfo; + +public class RequestHostnameProvider implements HostnameProvider { + + @Override + public String getHostname(UriInfo originalUriInfo) { + return originalUriInfo.getBaseUri().getHost(); + } + + @Override + public int getPort(UriInfo originalUriInfo) { + return originalUriInfo.getRequestUri().getPort(); + } + +} diff --git a/services/src/main/java/org/keycloak/url/RequestHostnameProviderFactory.java b/services/src/main/java/org/keycloak/url/RequestHostnameProviderFactory.java new file mode 100644 index 0000000000..d5cc384ba5 --- /dev/null +++ b/services/src/main/java/org/keycloak/url/RequestHostnameProviderFactory.java @@ -0,0 +1,21 @@ +package org.keycloak.url; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.urls.HostnameProvider; +import org.keycloak.urls.HostnameProviderFactory; + +import javax.ws.rs.core.UriInfo; + +public class RequestHostnameProviderFactory implements HostnameProviderFactory { + + @Override + public HostnameProvider create(KeycloakSession session) { + return new RequestHostnameProvider(); + } + + @Override + public String getId() { + return "request"; + } + +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.urls.HostnameProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.urls.HostnameProviderFactory new file mode 100644 index 0000000000..9177b0f466 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.urls.HostnameProviderFactory @@ -0,0 +1,2 @@ +org.keycloak.url.FixedHostnameProviderFactory +org.keycloak.url.RequestHostnameProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java index 8ce4d2ee05..4a6af70025 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java @@ -34,6 +34,7 @@ import org.junit.Assert; import org.keycloak.OAuth2Constants; import org.keycloak.RSATokenVerifier; import org.keycloak.admin.client.Keycloak; +import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.common.VerificationException; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.PemUtils; @@ -46,6 +47,7 @@ import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.IDToken; @@ -633,6 +635,14 @@ public class OAuthClient { } } + public OIDCConfigurationRepresentation doWellKnownRequest(String realm) { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + return SimpleHttp.doGet(AUTH_SERVER_ROOT + "/realms/" + realm + "/.well-known/openid-configuration", client).asJson(OIDCConfigurationRepresentation.class); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + public void closeClient(CloseableHttpClient client) { try { client.close(); @@ -729,7 +739,7 @@ public class OAuthClient { } public String getLoginFormUrl() { - UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_ROOT)); + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); if (responseType != null) { b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); } @@ -824,6 +834,11 @@ public class OAuthClient { return b.build(realm).toString(); } + public OAuthClient baseUrl(String baseUrl) { + this.baseUrl = baseUrl; + return this; + } + public OAuthClient realm(String realm) { this.realm = realm; return this; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 93778fd513..e47121c083 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -149,9 +149,7 @@ public abstract class AbstractKeycloakTest { public void beforeAbstractKeycloakTest() throws Exception { adminClient = testContext.getAdminClient(); if (adminClient == null || adminClient.isClosed()) { - String authServerContextRoot = suiteContext.getAuthServerInfo().getContextRoot().toString(); - adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), authServerContextRoot); - testContext.setAdminClient(adminClient); + reconnectAdminClient(); } getTestingClient(); @@ -181,6 +179,16 @@ public abstract class AbstractKeycloakTest { } + public void reconnectAdminClient() throws Exception { + if (adminClient != null && !adminClient.isClosed()) { + adminClient.close(); + } + + String authServerContextRoot = suiteContext.getAuthServerInfo().getContextRoot().toString(); + adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), authServerContextRoot); + testContext.setAdminClient(adminClient); + } + protected void beforeAbstractKeycloakTestRealmImport() throws Exception { } protected void postAfterAbstractKeycloak() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java new file mode 100644 index 0000000000..4b4828c140 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java @@ -0,0 +1,165 @@ +package org.keycloak.testsuite.url; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jboss.arquillian.container.test.api.ContainerController; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.junit.Test; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.util.OAuthClient; +import org.wildfly.extras.creaper.core.online.OnlineManagementClient; +import org.wildfly.extras.creaper.core.online.operations.admin.Administration; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +public class FixedHostnameTest extends AbstractKeycloakTest { + + @ArquillianResource + protected ContainerController controller; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + + RealmRepresentation customHostname = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + customHostname.setId("hostname"); + customHostname.setRealm("hostname"); + customHostname.setAttributes(new HashMap<>()); + customHostname.getAttributes().put("hostname", "custom-domain.127.0.0.1.nip.io"); + testRealms.add(customHostname); + } + + @Test + public void fixedHostname() throws Exception { + try { + assertWellKnown("test", "localhost"); + + configureFixedHostname(); + + assertWellKnown("test", "keycloak.127.0.0.1.nip.io"); + assertWellKnown("hostname", "custom-domain.127.0.0.1.nip.io"); + + assertTokenIssuer("test", "keycloak.127.0.0.1.nip.io"); + assertTokenIssuer("hostname", "custom-domain.127.0.0.1.nip.io"); + + assertInitialAccessTokenFromMasterRealm("test", "keycloak.127.0.0.1.nip.io"); + assertInitialAccessTokenFromMasterRealm("hostname", "custom-domain.127.0.0.1.nip.io"); + } finally { + clearFixedHostname(); + } + } + + private void assertInitialAccessTokenFromMasterRealm(String realm, String expectedHostname) throws JWSInputException, ClientRegistrationException { + ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation(); + rep.setCount(1); + rep.setExpiration(10000); + + ClientInitialAccessPresentation initialAccess = adminClient.realm(realm).clientInitialAccess().create(rep); + JsonWebToken token = new JWSInput(initialAccess.getToken()).readJsonContent(JsonWebToken.class); + assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); + + ClientRegistration clientReg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", realm).build(); + clientReg.auth(Auth.token(initialAccess.getToken())); + + ClientRepresentation client = new ClientRepresentation(); + client.setEnabled(true); + ClientRepresentation response = clientReg.create(client); + + String registrationAccessToken = response.getRegistrationAccessToken(); + JsonWebToken registrationToken = new JWSInput(registrationAccessToken).readJsonContent(JsonWebToken.class); + assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, registrationToken.getIssuer()); + } + + private void assertTokenIssuer(String realm, String expectedHostname) throws JWSInputException, IOException { + oauth.baseUrl("http://" + expectedHostname + ":8180/auth"); + + OAuthClient.AuthorizationEndpointResponse response = oauth.realm(realm).doLogin("test-user@localhost", "password"); + + OAuthClient.AccessTokenResponse tokenResponse = oauth.baseUrl(OAuthClient.AUTH_SERVER_ROOT).doAccessTokenRequest(response.getCode(), "password"); + + AccessToken token = new JWSInput(tokenResponse.getAccessToken()).readJsonContent(AccessToken.class); + assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); + + String introspection = oauth.introspectAccessTokenWithClientCredential(oauth.getClientId(), "password", tokenResponse.getAccessToken()); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode introspectionNode = objectMapper.readTree(introspection); + assertTrue(introspectionNode.get("active").asBoolean()); + assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, introspectionNode.get("iss").asText()); + } + + private void assertWellKnown(String realm, String expectedHostname) { + OIDCConfigurationRepresentation config = oauth.doWellKnownRequest(realm); + assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint()); + } + + private void configureFixedHostname() throws Exception { + if (suiteContext.getAuthServerInfo().isUndertow()) { + configureUndertow("fixed", "keycloak.127.0.0.1.nip.io", "8180", "8543"); + } else if (suiteContext.getAuthServerInfo().isJBossBased()) { + configureWildFly("fixed", "keycloak.127.0.0.1.nip.io", "8180", "8543"); + } else { + throw new RuntimeException("Don't know how to config"); + } + + reconnectAdminClient(); + + } + + private void clearFixedHostname() throws Exception { + if (suiteContext.getAuthServerInfo().isUndertow()) { + configureUndertow("request", "localhost", "-1", "-1"); + } else if (suiteContext.getAuthServerInfo().isJBossBased()) { + configureWildFly("request", "localhost", "-1", "-1"); + } else { + throw new RuntimeException("Don't know how to config"); + } + + reconnectAdminClient(); + } + + private void configureUndertow(String provider, String hostname, String httpPort, String httpsPort) { + controller.stop(suiteContext.getAuthServerInfo().getQualifier()); + + System.setProperty("keycloak.hostname.provider", provider); + System.setProperty("keycloak.hostname.fixed.hostname", hostname); + System.setProperty("keycloak.hostname.fixed.httpPort", httpPort); + System.setProperty("keycloak.hostname.fixed.httpsPort", httpsPort); + + controller.start(suiteContext.getAuthServerInfo().getQualifier()); + } + + private void configureWildFly(String provider, String hostname, String httpPort, String httpsPort) throws Exception { + OnlineManagementClient client = AuthServerTestEnricher.getManagementClient(); + Administration administration = new Administration(client); + + client.execute("/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value=" + provider + ")"); + client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.hostname,value=" + hostname + ")"); + client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.httpPort,value=" + httpPort + ")"); + client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.httpsPort,value=" + httpsPort + ")"); + + administration.reloadIfRequired(); + + client.close(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 3bb4d2a030..bc06479015 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -1,4 +1,15 @@ { + + "hostname": { + "provider": "${keycloak.hostname.provider:request}", + + "fixed": { + "hostname": "${keycloak.hostname.fixed.hostname:localhost}", + "httpPort": "${keycloak.hostname.fixed.httpPort:-1}", + "httpsPort": "${keycloak.hostname.fixed.httpPorts:-1}" + } + }, + "admin": { "realm": "master" }, @@ -85,7 +96,7 @@ "connectionsJpa": { "default": { - "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;MVCC=TRUE}", + "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1}", "driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}", "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", "user": "${keycloak.connectionsJpa.user:sa}", diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json index eb58977399..d7aa39a67d 100755 --- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json @@ -1,4 +1,15 @@ { + + "hostname": { + "provider": "request", + + "fixed": { + "hostname": "localhost", + "httpPort": "-1", + "httpsPort": "-1" + } + }, + "admin": { "realm": "master" }, @@ -61,7 +72,6 @@ "default": {} }, - "connectionsJpa": { "default": { "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1}", @@ -114,4 +124,4 @@ } } -} \ No newline at end of file +} diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 6f45c53ba5..93d19b3f30 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -108,6 +108,8 @@ sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired. offline-session-idle=Offline Session Idle offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire. +realm-detail.hostname=Hostname +realm-detail.hostname.tooltip=Set the hostname for the realm. Use in combination with the fixed hostname provider to override the server hostname for a specific realm. ## KEYCLOAK-7688 Offline Session Max for Offline Token offline-session-max-limited=Offline Session Max Limited diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html index dadb0c79ab..622bbe8c75 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html @@ -23,6 +23,14 @@ +
+ +
+ +
+ {{:: 'realm-detail.hostname.tooltip' | translate}} +
+
diff --git a/travis-run-tests.sh b/travis-run-tests.sh index c731f84710..6630ecfcd3 100755 --- a/travis-run-tests.sh +++ b/travis-run-tests.sh @@ -77,7 +77,7 @@ if [ $1 == "server-group3" ]; then fi if [ $1 == "server-group4" ]; then - run-server-tests org.keycloak.testsuite.k*.**.*Test,org.keycloak.testsuite.m*.**.*Test,org.keycloak.testsuite.o*.**.*Test,org.keycloak.testsuite.s*.**.*Test + run-server-tests org.keycloak.testsuite.k*.**.*Test,org.keycloak.testsuite.m*.**.*Test,org.keycloak.testsuite.o*.**.*Test,org.keycloak.testsuite.s*.**.*Test,org.keycloak.testsuite.u*.**.*Test fi if [ $1 == "crossdc-server" ]; then diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties index 7cfa64af9a..aaecc2ceba 100644 --- a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties +++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties @@ -77,4 +77,14 @@ keycloak.server.subsys.default.config=\ ${keycloak.x509cert.lookup.provider:default}\ \ \ + \ + request\ + \ + \ + \ + \ + \ + \ + \ + \ \ diff --git a/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli index f76cd45221..26c6a8aaf3 100644 --- a/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli +++ b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli @@ -1,5 +1,7 @@ /subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900) /subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) +/subsystem=keycloak-server/spi=hostname/:add(default-provider=request) +/subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) /subsystem=keycloak-server/spi=eventsStore/:add /subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) /subsystem=keycloak-server/spi=userCache/:add