KEYCLOAK-11728 New default hostname provider

Co-authored-by: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
stianst 2019-10-16 10:33:55 +02:00 committed by Stian Thorgersen
parent 7f1de02ca0
commit b8881b8ea0
89 changed files with 1396 additions and 373 deletions

View file

@ -676,4 +676,23 @@ if ((result.time == 100L) && (result.unit == MILLISECONDS)) of /profile=$cluster
echo
end-if
if (outcome == failed) of /profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/provider=default/:read-resource
echo Adding default hostname provider
/profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => "${keycloak.frontendUrl:}",forceBackendUrlToFrontendUrl => "false"},enabled=true)
end-if
if (result == request) of /profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
echo Switching from request to default hostname provider
/profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/:write-attribute(name=default-provider,value=default)
end-if
if (result != fixed) of /profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
try
/profile=$clusteredProfile/subsystem=keycloak-server/spi=hostname/provider=fixed:remove
echo Removed config for unused fixed hostname provider
catch
end-try
end-if
echo *** End Migration of /profile=$clusteredProfile ***

View file

@ -577,4 +577,23 @@ if ((result.time == 100L) && (result.unit == MILLISECONDS)) of /profile=$standal
echo
end-if
if (outcome == failed) of /profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/provider=default/:read-resource
echo Adding default hostname provider
/profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => "${keycloak.frontendUrl:}",forceBackendUrlToFrontendUrl => "false"},enabled=true)
end-if
if (result == request) of /profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
echo Switching from request to default hostname provider
/profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/:write-attribute(name=default-provider,value=default)
end-if
if (result != fixed) of /profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
try
/profile=$standaloneProfile/subsystem=keycloak-server/spi=hostname/provider=fixed:remove
echo Removed config for unused fixed hostname provider
catch
end-try
end-if
echo *** End Migration of /profile=$standaloneProfile ***

View file

@ -748,5 +748,23 @@ if (result == UP) of /subsystem=microprofile-health-smallrye:read-attribute(name
echo
end-if
if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/provider=default/:read-resource
echo Adding default hostname provider
/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => "${keycloak.frontendUrl:}",forceBackendUrlToFrontendUrl => "false"},enabled=true)
end-if
if (result == request) of /subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
echo Switching from request to default hostname provider
/subsystem=keycloak-server/spi=hostname/:write-attribute(name=default-provider,value=default)
end-if
if (result != fixed) of /subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
try
/subsystem=keycloak-server/spi=hostname/provider=fixed:remove
echo Removed config for unused fixed hostname provider
catch
end-try
end-if
echo *** End Migration ***

View file

@ -613,4 +613,23 @@ if (result == UP) of /subsystem=microprofile-health-smallrye:read-attribute(name
echo
end-if
if (outcome == failed) of /subsystem=keycloak-server/spi=hostname/provider=default/:read-resource
echo Adding default hostname provider
/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => "${keycloak.frontendUrl:}",forceBackendUrlToFrontendUrl => "false"},enabled=true)
end-if
if (result == request) of /subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
echo Switching from request to default hostname provider
/subsystem=keycloak-server/spi=hostname/:write-attribute(name=default-provider,value=default)
end-if
if (result != fixed) of /subsystem=keycloak-server/spi=hostname/:read-attribute(name=default-provider)
try
/subsystem=keycloak-server/spi=hostname/provider=fixed:remove
echo Removed config for unused fixed hostname provider
catch
end-try
end-if
echo *** End Migration ***

View file

@ -45,6 +45,7 @@ import org.keycloak.migration.migrators.MigrateTo4_0_0;
import org.keycloak.migration.migrators.MigrateTo4_2_0;
import org.keycloak.migration.migrators.MigrateTo4_6_0;
import org.keycloak.migration.migrators.MigrateTo6_0_0;
import org.keycloak.migration.migrators.MigrateTo8_0_0;
import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -81,7 +82,8 @@ public class MigrationModelManager {
new MigrateTo4_0_0(),
new MigrateTo4_2_0(),
new MigrateTo4_6_0(),
new MigrateTo6_0_0()
new MigrateTo6_0_0(),
new MigrateTo8_0_0()
};
public static void migrate(KeycloakSession session) {

View file

@ -0,0 +1,62 @@
/*
* Copyright 2019 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.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
import java.util.Collections;
public class MigrateTo8_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("8.0.0");
@Override
public ModelVersion getVersion() {
return VERSION;
}
@Override
public void migrate(KeycloakSession session) {
session.realms().getRealms().stream().forEach(realm -> migrateRealm(realm));
}
@Override
public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
migrateRealm(realm);
}
protected void migrateRealm(RealmModel realm) {
ClientModel adminConsoleClient = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
adminConsoleClient.setRootUrl(Constants.AUTH_ADMIN_URL_PROP);
String adminConsoleBaseUrl = "/admin/" + realm.getName() + "/console/";
adminConsoleClient.setBaseUrl(adminConsoleBaseUrl);
adminConsoleClient.setRedirectUris(Collections.singleton(adminConsoleBaseUrl + "*"));
adminConsoleClient.setWebOrigins(Collections.singleton("+"));
ClientModel accountClient = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
accountClient.setRootUrl(Constants.AUTH_BASE_URL_PROP);
String accountClientBaseUrl = "/realms/" + realm.getName() + "/account/";
accountClient.setBaseUrl(accountClientBaseUrl);
accountClient.setRedirectUris(Collections.singleton(accountClientBaseUrl + "*"));
}
}

View file

@ -26,27 +26,70 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class BrowserSecurityHeaders {
public static final String X_FRAME_OPTIONS = "X-Frame-Options";
public static final String X_FRAME_OPTIONS_DEFAULT = "SAMEORIGIN";
public static final String X_FRAME_OPTIONS_KEY = "xFrameOptions";
public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
public static final String CONTENT_SECURITY_POLICY_DEFAULT = "frame-src 'self'; frame-ancestors 'self'; object-src 'none';";
public static final String CONTENT_SECURITY_POLICY_KEY = "contentSecurityPolicy";
public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only";
public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT = "";
public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY = "contentSecurityPolicyReportOnly";
public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
public static final String X_CONTENT_TYPE_OPTIONS_DEFAULT = "nosniff";
public static final String X_CONTENT_TYPE_OPTIONS_KEY = "xContentTypeOptions";
public static final String X_ROBOTS_TAG = "X-Robots-Tag";
public static final String X_ROBOTS_TAG_KEY = "xRobotsTag";
public static final String X_ROBOTS_TAG_DEFAULT = "none";
public static final String X_XSS_PROTECTION = "X-XSS-Protection";
public static final String X_XSS_PROTECTION_DEFAULT = "1; mode=block";
public static final String X_XSS_PROTECTION_KEY = "xXSSProtection";
public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security";
public static final String STRICT_TRANSPORT_SECURITY_DEFAULT = "max-age=31536000; includeSubDomains";
public static final String STRICT_TRANSPORT_SECURITY_KEY = "strictTransportSecurity";
public static final Map<String, String> headerAttributeMap;
public static final Map<String, String> defaultHeaders;
static {
Map<String, String> headerMap = new HashMap<>();
headerMap.put("xFrameOptions", "X-Frame-Options");
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
headerMap.put("contentSecurityPolicyReportOnly", "Content-Security-Policy-Report-Only");
headerMap.put("xContentTypeOptions", "X-Content-Type-Options");
headerMap.put("xRobotsTag", "X-Robots-Tag");
headerMap.put("xXSSProtection", "X-XSS-Protection");
headerMap.put("strictTransportSecurity", "Strict-Transport-Security");
headerMap.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS);
headerMap.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY);
headerMap.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY);
headerMap.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS);
headerMap.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG);
headerMap.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION);
headerMap.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY);
Map<String, String> dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN");
dh.put("contentSecurityPolicy", "frame-src 'self'; frame-ancestors 'self'; object-src 'none';");
dh.put("contentSecurityPolicyReportOnly", "");
dh.put("xContentTypeOptions", "nosniff");
dh.put("xRobotsTag", "none");
dh.put("xXSSProtection", "1; mode=block");
dh.put("strictTransportSecurity", "max-age=31536000; includeSubDomains");
dh.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS_DEFAULT);
dh.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY_DEFAULT);
dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT);
dh.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS_DEFAULT);
dh.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG_DEFAULT);
dh.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION_DEFAULT);
dh.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY_DEFAULT);
defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);

View file

@ -36,6 +36,9 @@ public final class Constants {
public static final String BROKER_SERVICE_CLIENT_ID = "broker";
public static final String REALM_MANAGEMENT_CLIENT_ID = "realm-management";
public static final String AUTH_BASE_URL_PROP = "${authBaseUrl}";
public static final String AUTH_ADMIN_URL_PROP = "${authAdminUrl}";
public static final Collection<String> defaultClients = Arrays.asList(ACCOUNT_MANAGEMENT_CLIENT_ID, ADMIN_CLI_CLIENT_ID, BROKER_SERVICE_CLIENT_ID, REALM_MANAGEMENT_CLIENT_ID, ADMIN_CONSOLE_CLIENT_ID);
public static final String INSTALLED_APP_URN = "urn:ietf:wg:oauth:2.0:oob";

View file

@ -19,6 +19,7 @@ package org.keycloak.models;
import org.keycloak.common.ClientConnection;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.HttpHeaders;
import java.net.URI;
@ -33,8 +34,23 @@ public interface KeycloakContext {
String getContextPath();
/**
* Returns the URI assuming it is a frontend request. To resolve URI for a backend request use {@link #getUri(UrlType)}
* @return
*/
KeycloakUriInfo getUri();
/**
* Returns the URI. If a frontend request (from user-agent) @frontendRequest should be set to true. If a backend
* request (request from a client) should be set to false. Depending on the configure hostname provider it may
* return a hard-coded base URL for frontend request (for example https://auth.mycompany.com) and use the
* request URL for backend requests. Frontend URI should also be used for realm issuer fields in tokens.
*
* @param type the type of the request
* @return
*/
KeycloakUriInfo getUri(UrlType type);
HttpHeaders getRequestHeaders();
<T> T getContextObject(Class<T> clazz);

View file

@ -17,8 +17,8 @@
package org.keycloak.models;
import org.jboss.resteasy.specimpl.ResteasyUriBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;
@ -33,29 +33,38 @@ public class KeycloakUriInfo implements UriInfo {
private final String hostname;
private final String scheme;
private final int port;
private final String contextPath;
private URI absolutePath;
private URI requestURI;
private URI baseURI;
public KeycloakUriInfo(KeycloakSession session, UriInfo delegate) {
public KeycloakUriInfo(KeycloakSession session, UrlType type, UriInfo delegate) {
this.delegate = delegate;
HostnameProvider hostnameProvider = session.getProvider(HostnameProvider.class);
this.scheme = hostnameProvider.getScheme(delegate);
this.hostname = hostnameProvider.getHostname(delegate);
this.port = hostnameProvider.getPort(delegate);
this.scheme = hostnameProvider.getScheme(delegate, type);
this.hostname = hostnameProvider.getHostname(delegate, type);
this.port = hostnameProvider.getPort(delegate, type);
this.contextPath = hostnameProvider.getContextPath(delegate, type);
}
public UriInfo getDelegate() {
return delegate;
}
private UriBuilder initUriBuilder(UriBuilder b) {
b.scheme(scheme);
b.host(hostname);
b.port(port);
b.replacePath(contextPath);
return b;
}
@Override
public URI getRequestUri() {
if (requestURI == null) {
requestURI = delegate.getRequestUriBuilder().scheme(scheme).host(hostname).port(port).build();
requestURI = delegate.getRequestUri();
}
return requestURI;
}
@ -68,7 +77,7 @@ public class KeycloakUriInfo implements UriInfo {
@Override
public URI getAbsolutePath() {
if (absolutePath == null) {
absolutePath = delegate.getAbsolutePathBuilder().scheme(scheme).host(hostname).port(port).build();
absolutePath = delegate.getAbsolutePath();
}
return absolutePath;
}
@ -81,7 +90,7 @@ public class KeycloakUriInfo implements UriInfo {
@Override
public URI getBaseUri() {
if (baseURI == null) {
baseURI = delegate.getBaseUriBuilder().scheme(scheme).host(hostname).port(port).build();
baseURI = initUriBuilder(delegate.getBaseUriBuilder()).build();
}
return baseURI;
}

View file

@ -21,20 +21,99 @@ import org.keycloak.provider.Provider;
import javax.ws.rs.core.UriInfo;
/**
* The Hostname provider is used by Keycloak to decide URLs for frontend and backend requests. A provider can either
* base the URL on the request (Host header for example) or based on hard-coded URLs. Further, it is possible to have
* different URLs on frontend requests and backend requests.
*
* Note: Do NOT use {@link KeycloakContext#getUri()} within a Hostname provider. It will result in an infinite loop.
*/
public interface HostnameProvider extends Provider {
String getScheme(UriInfo originalUriInfo);
/**
* Returns the URL scheme. If not implemented will delegate to {@link #getScheme(UriInfo)}.
*
* @param originalUriInfo the original URI
* @param uype type of the request
* @return the schema
*/
default String getScheme(UriInfo originalUriInfo, UrlType type) {
return getScheme(originalUriInfo);
}
/**
* 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!
* Returns the URL scheme. If not implemented will get the scheme from the request.
*
* @param originalUriInfo the original UriInfo before hostname is replaced by the HostnameProvider
* @return the hostname
* @param originalUriInfo the original URI
* @return the schema
*/
String getHostname(UriInfo originalUriInfo);
default String getScheme(UriInfo originalUriInfo) {
return originalUriInfo.getBaseUri().getScheme();
}
int getPort(UriInfo originalUriInfo);
/**
* Returns the host. If not implemented will delegate to {@link #getHostname(UriInfo)}.
*
* @param originalUriInfo the original URI
* @param type type of the request
* @return the host
*/
default String getHostname(UriInfo originalUriInfo, UrlType type) {
return getHostname(originalUriInfo);
}
/**
* Returns the host. If not implemented will get the host from the request.
* @param originalUriInfo
* @return the host
*/
default String getHostname(UriInfo originalUriInfo) {
return originalUriInfo.getBaseUri().getHost();
}
/**
* Returns the port (or -1 for default port). If not implemented will delegate to {@link #getPort(UriInfo)}
*
* @param originalUriInfo the original URI
* @param type type of the request
* @return the port
*/
default int getPort(UriInfo originalUriInfo, UrlType type) {
return getPort(originalUriInfo);
}
/**
* Returns the port (or -1 for default port). If not implemented will get the port from the request.
*
* @param originalUriInfo the original URI
* @return the port
*/
default int getPort(UriInfo originalUriInfo) {
return originalUriInfo.getBaseUri().getPort();
}
/**
* Returns the context-path for Keycloak. This is useful when Keycloak is exposed on a different context-path on
* a reverse proxy. If not implemented will delegate to {@link #getContextPath(UriInfo)}
*
* @param originalUriInfo the original URI
* @param type type of the request
* @return the context-path
*/
default String getContextPath(UriInfo originalUriInfo, UrlType type) {
return getContextPath(originalUriInfo);
}
/**
* Returns the context-path for Keycloak This is useful when Keycloak is exposed on a different context-path on
* a reverse proxy. If not implemented will use the context-path from the request, which by default is /auth
*
* @param originalUriInfo the original URI
* @return the context-path
*/
default String getContextPath(UriInfo originalUriInfo) {
return originalUriInfo.getBaseUri().getPath();
}
@Override
default void close() {

View file

@ -0,0 +1,7 @@
package org.keycloak.urls;
public enum UrlType {
FRONTEND, BACKEND, ADMIN
}

View file

@ -58,8 +58,8 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
TokenUtils.checkThat(
// either redirect URI is not specified or must be valid for the client
t -> t.getRedirectUri() == null
|| RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), t.getRedirectUri(),
tokenContext.getRealm(), tokenContext.getAuthenticationSession().getClient()) != null,
|| RedirectUtils.verifyRedirectUri(tokenContext.getSession(), t.getRedirectUri(),
tokenContext.getAuthenticationSession().getClient()) != null,
Errors.INVALID_REDIRECT_URI,
Messages.INVALID_REDIRECT_URI
)
@ -88,8 +88,7 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
.createInfoPage();
}
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(),
tokenContext.getRealm(), authSession.getClient());
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getSession(), token.getRedirectUri(), authSession.getClient());
if (redirectUri != null) {
authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true");

View file

@ -236,7 +236,7 @@ public class AuthorizationTokenService {
private Response createSuccessfulResponse(Object response, KeycloakAuthorizationRequest request) {
return Cors.add(request.getHttpRequest(), Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
.allowedOrigins(request.getKeycloakSession().getContext().getUri(), request.getKeycloakSession().getContext().getClient())
.allowedOrigins(request.getKeycloakSession(), request.getKeycloakSession().getContext().getClient())
.allowedMethods(HttpMethod.POST)
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}

View file

@ -110,7 +110,6 @@ public class ImportUtils {
}
RealmManager realmManager = new RealmManager(session);
realmManager.setContextPath(session.getContext().getContextPath());
realmManager.importRealm(rep, skipUserDependent);
if (System.getProperty(ExportImportConfig.ACTION) != null) {

View file

@ -30,6 +30,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.storage.StorageId;
import java.util.ArrayList;
@ -47,7 +48,6 @@ public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<>();
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
for (ClientModel client : getApplications(session, realm, user)) {
@ -90,7 +90,7 @@ public class ApplicationsBean {
additionalGrants.add("${offlineToken}");
}
applications.add(new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants));
applications.add(new ApplicationEntry(session, realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants));
}
}
@ -142,14 +142,16 @@ public class ApplicationsBean {
public static class ApplicationEntry {
private KeycloakSession session;
private final List<RoleModel> realmRolesAvailable;
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable;
private final ClientModel client;
private final List<String> clientScopesGranted;
private final List<String> additionalGrants;
public ApplicationEntry(List<RoleModel> realmRolesAvailable, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable,
public ApplicationEntry(KeycloakSession session, List<RoleModel> realmRolesAvailable, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable,
ClientModel client, List<String> clientScopesGranted, List<String> additionalGrants) {
this.session = session;
this.realmRolesAvailable = realmRolesAvailable;
this.resourceRolesAvailable = resourceRolesAvailable;
this.client = client;
@ -170,44 +172,7 @@ public class ApplicationsBean {
}
public String getEffectiveUrl() {
String rootUrl = getClient().getRootUrl();
String baseUrl = getClient().getBaseUrl();
if (rootUrl == null) rootUrl = "";
if (baseUrl == null) baseUrl = "";
if (rootUrl.equals("") && baseUrl.equals("")) {
return "";
}
if (rootUrl.equals("") && !baseUrl.equals("")) {
return baseUrl;
}
if (!rootUrl.equals("") && baseUrl.equals("")) {
return rootUrl;
}
if (isBaseUrlRelative() && !rootUrl.equals("")) {
return concatUrls(rootUrl, baseUrl);
}
return baseUrl;
}
private String concatUrls(String u1, String u2) {
if (u1.endsWith("/")) u1 = u1.substring(0, u1.length() - 1);
if (u2.startsWith("/")) u2 = u2.substring(1);
return u1 + "/" + u2;
}
private boolean isBaseUrlRelative() {
String baseUrl = getClient().getBaseUrl();
if (baseUrl.equals("")) return false;
if (baseUrl.startsWith("/")) return true;
if (baseUrl.startsWith("./")) return true;
if (baseUrl.startsWith("../")) return true;
return false;
return ResolveRelative.resolveRelativeUri(session, getClient().getRootUrl(), getClient().getBaseUrl());
}
public ClientModel getClient() {

View file

@ -378,7 +378,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
URI baseUriWithCodeAndClientId = baseUriBuilder.build();
if (client != null) {
attributes.put("client", new ClientBean(client, baseUri));
attributes.put("client", new ClientBean(session, client));
}
if (realm != null) {

View file

@ -18,6 +18,7 @@
package org.keycloak.forms.login.freemarker.model;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.util.ResolveRelative;
import java.net.URI;
@ -29,13 +30,12 @@ import java.util.Map;
*/
public class ClientBean {
private KeycloakSession session;
protected ClientModel client;
private URI requestUri;
public ClientBean(ClientModel client, URI requestUri) {
public ClientBean(KeycloakSession session, ClientModel client) {
this.session = session;
this.client = client;
this.requestUri = requestUri;
}
public String getClientId() {
@ -51,7 +51,7 @@ public class ClientBean {
}
public String getBaseUrl() {
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
return ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), client.getBaseUrl());
}
public Map<String,String> getAttributes(){

View file

@ -71,7 +71,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientModel(client);
if (config.isUseJwksUrl()) {
String jwksUrl = config.getJwksUrl();
jwksUrl = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), jwksUrl);
jwksUrl = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), jwksUrl);
JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
} else if (keyUse == JWK.Use.SIG) {

View file

@ -58,7 +58,7 @@ public class DockerComposeYamlInstallationProvider implements ClientInstallation
final ZipOutputStream zipOutput = new ZipOutputStream(byteStream);
try {
return generateInstallation(zipOutput, byteStream, session.keys().getActiveRsaKey(realm).getCertificate(), session.getContext().getAuthServerUrl().toURL(), realm.getName(), client.getClientId());
return generateInstallation(zipOutput, byteStream, session.keys().getActiveRsaKey(realm).getCertificate(), session.getContext().getUri().getBaseUri().toURL(), realm.getName(), client.getClientId());
} catch (final IOException e) {
try {
zipOutput.close();

View file

@ -310,7 +310,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override
public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
new ResourceAdminManager(session).logoutClientSession(realm, client, clientSession);
}
@Override

View file

@ -37,6 +37,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.clientregistration.ClientRegistrationService;
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.urls.UrlType;
import org.keycloak.wellknown.WellKnownProvider;
import javax.ws.rs.core.UriBuilder;
@ -75,21 +76,24 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
@Override
public Object getConfig() {
UriInfo uriInfo = session.getContext().getUri();
UriInfo frontendUriInfo = session.getContext().getUri(UrlType.FRONTEND);
UriInfo backendUriInfo = session.getContext().getUri(UrlType.BACKEND);
RealmModel realm = session.getContext().getRealm();
UriBuilder uriBuilder = RealmsResource.protocolUrl(uriInfo);
UriBuilder frontendUriBuilder = RealmsResource.protocolUrl(frontendUriInfo);
UriBuilder backendUriBuilder = RealmsResource.protocolUrl(backendUriInfo);
OIDCConfigurationRepresentation config = new OIDCConfigurationRepresentation();
config.setIssuer(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
config.setAuthorizationEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setTokenEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setTokenIntrospectionEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").path(TokenEndpoint.class, "introspect").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setCheckSessionIframe(uriBuilder.clone().path(OIDCLoginProtocolService.class, "getLoginStatusIframe").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
config.setIssuer(Urls.realmIssuer(frontendUriInfo.getBaseUri(), realm.getName()));
config.setAuthorizationEndpoint(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setTokenEndpoint(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setTokenIntrospectionEndpoint(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "token").path(TokenEndpoint.class, "introspect").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setUserinfoEndpoint(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setLogoutEndpoint(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setJwksUri(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setCheckSessionIframe(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "getLoginStatusIframe").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(backendUriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
config.setIdTokenSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
config.setIdTokenEncryptionAlgValuesSupported(getSupportedIdTokenEncryptionAlg(false));

View file

@ -410,7 +410,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
event.detail(Details.REDIRECT_URI, redirectUriParam);
// redirect_uri parameter is required per OpenID Connect, but optional per OAuth2
redirectUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), redirectUriParam, realm, client, isOIDCRequest);
redirectUri = RedirectUtils.verifyRedirectUri(session, redirectUriParam, client, isOIDCRequest);
if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);

View file

@ -77,7 +77,7 @@ public class LoginStatusIframeEndpoint {
RealmModel realm = session.getContext().getRealm();
ClientModel client = session.realms().getClientByClientId(clientId, realm);
if (client != null && client.isEnabled()) {
Set<String> validWebOrigins = WebOriginsUtils.resolveValidWebOrigins(uriInfo, client);
Set<String> validWebOrigins = WebOriginsUtils.resolveValidWebOrigins(session, client);
validWebOrigins.add(UriUtils.getOrigin(uriInfo.getRequestUri()));
if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) {
return Response.noContent().build();

View file

@ -105,7 +105,7 @@ public class LogoutEndpoint {
String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
if (redirect != null) {
String validatedUri = RedirectUtils.verifyRealmRedirectUri(session.getContext().getUri(), redirect, realm);
String validatedUri = RedirectUtils.verifyRealmRedirectUri(session, redirect);
if (validatedUri == null) {
event.event(EventType.LOGOUT);
event.detail(Details.REDIRECT_URI, redirect);
@ -216,7 +216,7 @@ public class LogoutEndpoint {
}
}
return Cors.add(request, Response.noContent()).auth().allowedOrigins(session.getContext().getUri(), client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
return Cors.add(request, Response.noContent()).auth().allowedOrigins(session, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
private void logout(UserSessionModel userSession, boolean offline) {

View file

@ -233,7 +233,7 @@ public class TokenEndpoint {
client = clientAuth.getClient();
clientAuthAttributes = clientAuth.getClientAuthAttributes();
cors.allowedOrigins(session.getContext().getUri(), client);
cors.allowedOrigins(session, client);
if (client.isBearerOnly()) {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_CLIENT, "Bearer-only not allowed", Response.Status.BAD_REQUEST);
@ -1093,7 +1093,7 @@ public class TokenEndpoint {
session.getContext().setClient(client);
cors.allowedOrigins(session.getContext().getUri(), client);
cors.allowedOrigins(session, client);
}
String claimToken = null;

View file

@ -75,11 +75,10 @@ public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
ClientModel client = clientSessionCtx.getClientSession().getClient();
UriInfo uriInfo = session.getContext().getUri();
Set<String> allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null && !allowedOrigins.isEmpty()) {
token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(session, client));
}
return token;

View file

@ -21,10 +21,12 @@ import org.jboss.logging.Logger;
import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.RealmModel;
import org.keycloak.services.Urls;
import org.keycloak.services.util.ResolveRelative;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
@ -38,46 +40,49 @@ public class RedirectUtils {
private static final Logger logger = Logger.getLogger(RedirectUtils.class);
public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
Set<String> validRedirects = getValidateRedirectUris(uriInfo, realm);
return verifyRedirectUri(uriInfo, null, redirectUri, realm, validRedirects, true);
public static String verifyRealmRedirectUri(KeycloakSession session, String redirectUri) {
Set<String> validRedirects = getValidateRedirectUris(session);
return verifyRedirectUri(session, null, redirectUri, validRedirects, true);
}
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
return verifyRedirectUri(uriInfo, redirectUri, realm, client, true);
public static String verifyRedirectUri(KeycloakSession session, String redirectUri, ClientModel client) {
return verifyRedirectUri(session, redirectUri, client, true);
}
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client, boolean requireRedirectUri) {
public static String verifyRedirectUri(KeycloakSession session, String redirectUri, ClientModel client, boolean requireRedirectUri) {
if (client != null)
return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris(), requireRedirectUri);
return verifyRedirectUri(session, client.getRootUrl(), redirectUri, client.getRedirectUris(), requireRedirectUri);
return null;
}
public static Set<String> resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set<String> validRedirects) {
public static Set<String> resolveValidRedirects(KeycloakSession session, String rootUrl, Set<String> validRedirects) {
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
Set<String> resolveValidRedirects = new HashSet<>();
for (String validRedirect : validRedirects) {
resolveValidRedirects.add(validRedirect); // add even relative urls.
if (validRedirect.startsWith("/")) {
validRedirect = relativeToAbsoluteURI(uriInfo, rootUrl, validRedirect);
validRedirect = relativeToAbsoluteURI(session, rootUrl, validRedirect);
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
resolveValidRedirects.add(validRedirect);
} else {
resolveValidRedirects.add(validRedirect);
}
}
return resolveValidRedirects;
}
private static Set<String> getValidateRedirectUris(UriInfo uriInfo, RealmModel realm) {
private static Set<String> getValidateRedirectUris(KeycloakSession session) {
Set<String> redirects = new HashSet<>();
for (ClientModel client : realm.getClients()) {
for (ClientModel client : session.getContext().getRealm().getClients()) {
if (client.isEnabled()) {
redirects.addAll(resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris()));
redirects.addAll(resolveValidRedirects(session, client.getRootUrl(), client.getRedirectUris()));
}
}
return redirects;
}
private static String verifyRedirectUri(UriInfo uriInfo, String rootUrl, String redirectUri, RealmModel realm, Set<String> validRedirects, boolean requireRedirectUri) {
private static String verifyRedirectUri(KeycloakSession session, String rootUrl, String redirectUri, Set<String> validRedirects, boolean requireRedirectUri) {
KeycloakUriInfo uriInfo = session.getContext().getUri();
RealmModel realm = session.getContext().getRealm();
if (redirectUri != null)
redirectUri = normalizeUrl(redirectUri);
@ -98,7 +103,7 @@ public class RedirectUtils {
redirectUri = lowerCaseHostname(redirectUri);
String r = redirectUri;
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, rootUrl, validRedirects);
Set<String> resolveValidRedirects = resolveValidRedirects(session, rootUrl, validRedirects);
boolean valid = matchesRedirects(resolveValidRedirects, r);
@ -118,7 +123,7 @@ public class RedirectUtils {
valid = matchesRedirects(resolveValidRedirects, r);
}
if (valid && redirectUri.startsWith("/")) {
redirectUri = relativeToAbsoluteURI(uriInfo, rootUrl, redirectUri);
redirectUri = relativeToAbsoluteURI(session, rootUrl, redirectUri);
}
redirectUri = valid ? redirectUri : null;
}
@ -139,9 +144,13 @@ public class RedirectUtils {
}
}
private static String relativeToAbsoluteURI(UriInfo uriInfo, String rootUrl, String relative) {
private static String relativeToAbsoluteURI(KeycloakSession session, String rootUrl, String relative) {
if (rootUrl != null) {
rootUrl = ResolveRelative.resolveRootUrl(session, rootUrl);
}
if (rootUrl == null || rootUrl.isEmpty()) {
rootUrl = UriUtils.getOrigin(uriInfo.getBaseUri());
rootUrl = UriUtils.getOrigin(session.getContext().getUri().getBaseUri());
}
StringBuilder sb = new StringBuilder();
sb.append(rootUrl);

View file

@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.utils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import javax.ws.rs.core.UriInfo;
import java.util.HashSet;
@ -31,14 +32,14 @@ public class WebOriginsUtils {
public static final String INCLUDE_REDIRECTS = "+";
public static Set<String> resolveValidWebOrigins(UriInfo uriInfo, ClientModel client) {
public static Set<String> resolveValidWebOrigins(KeycloakSession session, ClientModel client) {
Set<String> origins = new HashSet<>();
if (client.getWebOrigins() != null) {
origins.addAll(client.getWebOrigins());
}
if (origins.contains(INCLUDE_REDIRECTS)) {
origins.remove(INCLUDE_REDIRECTS);
for (String redirectUri : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
for (String redirectUri : RedirectUtils.resolveValidRedirects(session, client.getRootUrl(), client.getRedirectUris())) {
if (redirectUri.startsWith("http://") || redirectUri.startsWith("https://")) {
origins.add(UriUtils.getOrigin(redirectUri));
}

View file

@ -546,7 +546,7 @@ public class SamlProtocol implements LoginProtocol {
roleListMapper.mapper.mapRoles(existingAttributeStatement, roleListMapper.model, session, userSession, clientSessionCtx);
}
public static String getLogoutServiceUrl(UriInfo uriInfo, ClientModel client, String bindingType) {
public static String getLogoutServiceUrl(KeycloakSession session, ClientModel client, String bindingType) {
String logoutServiceUrl = null;
if (SAML_POST_BINDING.equals(bindingType)) {
logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
@ -557,7 +557,7 @@ public class SamlProtocol implements LoginProtocol {
logoutServiceUrl = client.getManagementUrl();
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals(""))
return null;
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
return ResourceAdminManager.resolveUri(session, client.getRootUrl(), logoutServiceUrl);
}
@ -567,7 +567,7 @@ public class SamlProtocol implements LoginProtocol {
SamlClient samlClient = new SamlClient(client);
try {
boolean postBinding = isLogoutPostBindingForClient(clientSession);
String bindingUri = getLogoutServiceUrl(uriInfo, client, postBinding ? SAML_POST_BINDING : SAML_REDIRECT_BINDING);
String bindingUri = getLogoutServiceUrl(session, client, postBinding ? SAML_POST_BINDING : SAML_REDIRECT_BINDING);
if (bindingUri == null) {
logger.warnf("Failed to logout client %s, skipping this client. Please configure the logout service url in the admin console for your client applications.", client.getClientId());
return null;
@ -672,7 +672,7 @@ public class SamlProtocol implements LoginProtocol {
public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
SamlClient samlClient = new SamlClient(client);
String logoutUrl = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
String logoutUrl = getLogoutServiceUrl(session, client, SAML_POST_BINDING);
if (logoutUrl == null) {
logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId());
return;

View file

@ -299,7 +299,7 @@ public class SamlService extends AuthorizationEndpointBase {
String redirect;
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
if (redirectUri != null && ! "null".equals(redirectUri.toString())) { // "null" is for testing purposes
redirect = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), redirectUri.toString(), realm, client);
redirect = RedirectUtils.verifyRedirectUri(session, redirectUri.toString(), client);
} else {
if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
@ -413,12 +413,12 @@ public class SamlService extends AuthorizationEndpointBase {
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
String logoutBinding = getBindingType();
String postBindingUri = SamlProtocol.getLogoutServiceUrl(session.getContext().getUri(), client, SamlProtocol.SAML_POST_BINDING);
String postBindingUri = SamlProtocol.getLogoutServiceUrl(session, client, SamlProtocol.SAML_POST_BINDING);
if (samlClient.forcePostBinding() && postBindingUri != null && ! postBindingUri.trim().isEmpty())
logoutBinding = SamlProtocol.SAML_POST_BINDING;
boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
String bindingUri = SamlProtocol.getLogoutServiceUrl(session.getContext().getUri(), client, logoutBinding);
String bindingUri = SamlProtocol.getLogoutServiceUrl(session, client, logoutBinding);
UserSessionModel userSession = authResult.getSession();
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
if (samlClient.requiresRealmSignature()) {
@ -474,7 +474,7 @@ public class SamlService extends AuthorizationEndpointBase {
// default
String logoutBinding = getBindingType();
String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(session.getContext().getUri(), client, logoutBinding);
String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(session, client, logoutBinding);
String logoutRelayState = relayState;
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(logoutRequest.getID());

View file

@ -28,11 +28,14 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -47,7 +50,7 @@ public class DefaultKeycloakContext implements KeycloakContext {
private KeycloakSession session;
private KeycloakUriInfo uriInfo;
private Map<UrlType, KeycloakUriInfo> uriInfo;
private AuthenticationSessionModel authenticationSession;
@ -57,24 +60,30 @@ public class DefaultKeycloakContext implements KeycloakContext {
@Override
public URI getAuthServerUrl() {
UriInfo uri = getUri();
KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
return keycloakApplication.getBaseUri(uri);
return getUri(UrlType.FRONTEND).getBaseUri();
}
@Override
public String getContextPath() {
KeycloakApplication app = getContextObject(KeycloakApplication.class);
if (app == null) return null;
return app.getContextPath();
return getUri(UrlType.FRONTEND).getBaseUri().getPath();
}
@Override
public KeycloakUriInfo getUri(UrlType type) {
if (uriInfo == null || !uriInfo.containsKey(type)) {
if (uriInfo == null) {
uriInfo = new HashMap<>();
}
UriInfo originalUriInfo = getContextObject(UriInfo.class);
uriInfo.put(type, new KeycloakUriInfo(session, type, originalUriInfo));
}
return uriInfo.get(type);
}
@Override
public KeycloakUriInfo getUri() {
if (uriInfo == null) {
uriInfo = new KeycloakUriInfo(session, getContextObject(UriInfo.class));
}
return uriInfo;
return getUri(UrlType.FRONTEND);
}
@Override

View file

@ -54,7 +54,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
auth.requireView(client);
ClientManager clientManager = new ClientManager(new RealmManager(session));
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getUri().getBaseUri());
event.client(client.getClientId()).success();
return Response.ok(rep).build();

View file

@ -55,7 +55,7 @@ public class ApplianceBootstrap {
return session.users().getUsersCount(realm) == 0;
}
public boolean createMasterRealm(String contextPath) {
public boolean createMasterRealm() {
if (!isNewInstall()) {
throw new IllegalStateException("Can't create default realm as realms already exists");
}
@ -64,7 +64,6 @@ public class ApplianceBootstrap {
ServicesLogger.LOGGER.initializingAdminRealm(adminRealmName);
RealmManager manager = new RealmManager(session);
manager.setContextPath(contextPath);
RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
realm.setName(adminRealmName);
realm.setDisplayName(Version.NAME);

View file

@ -67,15 +67,6 @@ public class RealmManager {
protected KeycloakSession session;
protected RealmProvider model;
protected String contextPath = "";
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public RealmManager(KeycloakSession session) {
this.session = session;
@ -165,11 +156,15 @@ public class RealmManager {
ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
if (adminConsole == null) adminConsole = KeycloakModelUtils.createClient(realm, Constants.ADMIN_CONSOLE_CLIENT_ID);
adminConsole.setName("${client_" + Constants.ADMIN_CONSOLE_CLIENT_ID + "}");
String baseUrl = contextPath + "/admin/" + realm.getName() + "/console";
adminConsole.setBaseUrl(baseUrl + "/index.html");
adminConsole.setRootUrl(Constants.AUTH_ADMIN_URL_PROP);
String baseUrl = "/admin/" + realm.getName() + "/console/";
adminConsole.setBaseUrl(baseUrl);
adminConsole.addRedirectUri(baseUrl + "*");
adminConsole.setWebOrigins(Collections.singleton("+"));
adminConsole.setEnabled(true);
adminConsole.setPublicClient(true);
adminConsole.addRedirectUri(baseUrl + "/*");
adminConsole.setFullScopeAllowed(false);
adminConsole.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
}
@ -412,10 +407,12 @@ public class RealmManager {
client.setName("${client_" + Constants.ACCOUNT_MANAGEMENT_CLIENT_ID + "}");
client.setEnabled(true);
client.setFullScopeAllowed(false);
String base = contextPath + "/realms/" + realm.getName() + "/account";
String redirectUri = base + "/*";
client.addRedirectUri(redirectUri);
client.setBaseUrl(base);
client.setRootUrl(Constants.AUTH_BASE_URL_PROP);
String baseUrl = "/realms/" + realm.getName() + "/account/";
client.setBaseUrl(baseUrl);
client.addRedirectUri(baseUrl + "*");
client.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
for (String role : AccountRoles.ALL) {

View file

@ -64,19 +64,19 @@ public class ResourceAdminManager {
this.session = session;
}
public static String resolveUri(URI requestUri, String rootUrl, String uri) {
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, rootUrl, uri);
public static String resolveUri(KeycloakSession session, String rootUrl, String uri) {
String absoluteURI = ResolveRelative.resolveRelativeUri(session, rootUrl, uri);
return StringPropertyReplacer.replaceProperties(absoluteURI);
}
public static String getManagementUrl(URI requestUri, ClientModel client) {
public static String getManagementUrl(KeycloakSession session, ClientModel client) {
String mgmtUrl = client.getManagementUrl();
if (mgmtUrl == null || mgmtUrl.equals("")) {
return null;
}
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), mgmtUrl);
String absoluteURI = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), mgmtUrl);
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
return StringPropertyReplacer.replaceProperties(absoluteURI);
@ -84,8 +84,8 @@ public class ResourceAdminManager {
// For non-cluster setup, return just single configured managementUrls
// For cluster setup, return the management Urls corresponding to all registered cluster nodes
private List<String> getAllManagementUrls(URI requestUri, ClientModel client) {
String baseMgmtUrl = getManagementUrl(requestUri, client);
private List<String> getAllManagementUrls(ClientModel client) {
String baseMgmtUrl = getManagementUrl(session, client);
if (baseMgmtUrl == null) {
return Collections.emptyList();
}
@ -107,14 +107,14 @@ public class ResourceAdminManager {
return result;
}
public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
public void logoutUser(RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
logoutUserSessions(requestUri, realm, userSessions);
logoutUserSessions(realm, userSessions);
}
protected void logoutUserSessions(URI requestUri, RealmModel realm, List<UserSessionModel> userSessions) {
protected void logoutUserSessions(RealmModel realm, List<UserSessionModel> userSessions) {
// Map from "app" to clientSessions for this app
MultivaluedHashMap<String, AuthenticatedClientSessionModel> clientSessions = new MultivaluedHashMap<>();
for (UserSessionModel userSession : userSessions) {
@ -128,7 +128,7 @@ public class ResourceAdminManager {
if (entry.getValue().size() == 0) {
continue;
}
logoutClientSessions(requestUri, realm, entry.getValue().get(0).getClient(), entry.getValue());
logoutClientSessions(realm, entry.getValue().get(0).getClient(), entry.getValue());
}
}
@ -139,12 +139,12 @@ public class ResourceAdminManager {
}
public boolean logoutClientSession(URI requestUri, RealmModel realm, ClientModel resource, AuthenticatedClientSessionModel clientSession) {
return logoutClientSessions(requestUri, realm, resource, Arrays.asList(clientSession));
public boolean logoutClientSession(RealmModel realm, ClientModel resource, AuthenticatedClientSessionModel clientSession) {
return logoutClientSessions(realm, resource, Arrays.asList(clientSession));
}
protected boolean logoutClientSessions(URI requestUri, RealmModel realm, ClientModel resource, List<AuthenticatedClientSessionModel> clientSessions) {
String managementUrl = getManagementUrl(requestUri, resource);
protected boolean logoutClientSessions(RealmModel realm, ClientModel resource, List<AuthenticatedClientSessionModel> clientSessions) {
String managementUrl = getManagementUrl(session, resource);
if (managementUrl != null) {
// Key is host, value is list of http sessions for this host
@ -195,27 +195,27 @@ public class ResourceAdminManager {
// Methods for logout all
public GlobalRequestResult logoutAll(URI requestUri, RealmModel realm) {
public GlobalRequestResult logoutAll(RealmModel realm) {
realm.setNotBefore(Time.currentTime());
List<ClientModel> resources = realm.getClients();
logger.debugv("logging out {0} resources ", resources.size());
GlobalRequestResult finalResult = new GlobalRequestResult();
for (ClientModel resource : resources) {
GlobalRequestResult currentResult = logoutClient(requestUri, realm, resource, realm.getNotBefore());
GlobalRequestResult currentResult = logoutClient(realm, resource, realm.getNotBefore());
finalResult.addAll(currentResult);
}
return finalResult;
}
public GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource) {
public GlobalRequestResult logoutClient(RealmModel realm, ClientModel resource) {
resource.setNotBefore(Time.currentTime());
return logoutClient(requestUri, realm, resource, resource.getNotBefore());
return logoutClient(realm, resource, resource.getNotBefore());
}
protected GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, resource);
protected GlobalRequestResult logoutClient(RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(resource);
if (mgmtUrls.isEmpty()) {
logger.debug("No management URL or no registered cluster nodes for the client " + resource.getClientId());
return new GlobalRequestResult();
@ -251,22 +251,22 @@ public class ResourceAdminManager {
}
}
public GlobalRequestResult pushRealmRevocationPolicy(URI requestUri, RealmModel realm) {
public GlobalRequestResult pushRealmRevocationPolicy(RealmModel realm) {
GlobalRequestResult finalResult = new GlobalRequestResult();
for (ClientModel client : realm.getClients()) {
GlobalRequestResult currentResult = pushRevocationPolicy(requestUri, realm, client, realm.getNotBefore());
GlobalRequestResult currentResult = pushRevocationPolicy(realm, client, realm.getNotBefore());
finalResult.addAll(currentResult);
}
return finalResult;
}
public GlobalRequestResult pushClientRevocationPolicy(URI requestUri, RealmModel realm, ClientModel client) {
return pushRevocationPolicy(requestUri, realm, client, client.getNotBefore());
public GlobalRequestResult pushClientRevocationPolicy(RealmModel realm, ClientModel client) {
return pushRevocationPolicy(realm, client, client.getNotBefore());
}
protected GlobalRequestResult pushRevocationPolicy(URI requestUri, RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, resource);
protected GlobalRequestResult pushRevocationPolicy(RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(resource);
if (mgmtUrls.isEmpty()) {
logger.debugf("No management URL or no registered cluster nodes for the client %s", resource.getClientId());
return new GlobalRequestResult();
@ -297,8 +297,8 @@ public class ResourceAdminManager {
: loginProtocol.sendPushRevocationPolicyRequest(realm, resource, notBefore, managementUrl);
}
public GlobalRequestResult testNodesAvailability(URI requestUri, RealmModel realm, ClientModel client) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, client);
public GlobalRequestResult testNodesAvailability(RealmModel realm, ClientModel client) {
List<String> mgmtUrls = getAllManagementUrls(client);
if (mgmtUrls.isEmpty()) {
logger.debug("No management URL or no registered cluster nodes for the application " + client.getClientId());
return new GlobalRequestResult();

View file

@ -30,6 +30,7 @@ import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.representations.AccessToken;
@ -103,9 +104,9 @@ public class Cors {
return this;
}
public Cors allowedOrigins(UriInfo uriInfo, ClientModel client) {
public Cors allowedOrigins(KeycloakSession session, ClientModel client) {
if (client != null) {
allowedOrigins = WebOriginsUtils.resolveValidWebOrigins(uriInfo, client);
allowedOrigins = WebOriginsUtils.resolveValidWebOrigins(session, client);
}
return this;
}

View file

@ -213,7 +213,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING);
checkRealm();
ClientModel client = checkClient(clientId);
redirectUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), redirectUri, realmModel, client);
redirectUri = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
@ -1258,7 +1258,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
private Response corsResponse(Response response, ClientModel clientModel) {
return Cors.add(this.request, Response.fromResponse(response)).auth().allowedOrigins(session.getContext().getUri(), clientModel).build();
return Cors.add(this.request, Response.fromResponse(response)).auth().allowedOrigins(session, clientModel).build();
}
private void fireErrorEvent(String message, Throwable throwable) {

View file

@ -107,7 +107,6 @@ public class KeycloakApplication extends Application {
protected Set<Class<?>> classes = new HashSet<Class<?>>();
protected KeycloakSessionFactory sessionFactory;
protected String contextPath;
public KeycloakApplication() {
@ -123,7 +122,6 @@ public class KeycloakApplication extends Application {
loadConfig(context);
this.contextPath = context.getContextPath();
this.sessionFactory = createSessionFactory();
Resteasy.pushDefaultContextObject(KeycloakApplication.class, this);
@ -243,7 +241,7 @@ public class KeycloakApplication extends Application {
}
if (createMasterRealm) {
applianceBootstrap.createMasterRealm(contextPath);
applianceBootstrap.createMasterRealm();
}
session.getTransactionManager().commit();
} catch (RuntimeException re) {
@ -281,20 +279,6 @@ public class KeycloakApplication extends Application {
}
}
public String getContextPath() {
return contextPath;
}
/**
* Get base URI of WAR distribution, not JAX-RS
*
* @param uriInfo
* @return
*/
public URI getBaseUri(UriInfo uriInfo) {
return uriInfo.getBaseUriBuilder().replacePath(getContextPath()).build();
}
public static void loadConfig(ServletContext context) {
try {
JsonNode node = null;
@ -410,7 +394,6 @@ public class KeycloakApplication extends Application {
try {
RealmManager manager = new RealmManager(session);
manager.setContextPath(getContextPath());
if (rep.getId() != null && manager.getRealm(rep.getId()) != null) {
ServicesLogger.LOGGER.realmExists(rep.getRealm(), from);

View file

@ -222,7 +222,7 @@ public class LoginActionsServiceChecks {
ClientModel client = context.getAuthenticationSession().getClient();
if (RedirectUtils.verifyRedirectUri(context.getUriInfo(), redirectUri, context.getRealm(), client) == null) {
if (RedirectUtils.verifyRedirectUri(context.getSession(), redirectUri, client) == null) {
throw new ExplainedTokenVerificationException(t, Errors.INVALID_REDIRECT_URI, Messages.INVALID_REDIRECT_URI);
}

View file

@ -160,7 +160,7 @@ public class RealmsResource {
if (client.getRootUrl() != null && (client.getBaseUrl() == null || client.getBaseUrl().isEmpty())) {
targetUri = KeycloakUriBuilder.fromUri(client.getRootUrl()).build();
} else {
targetUri = KeycloakUriBuilder.fromUri(ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), client.getBaseUrl())).build();
targetUri = KeycloakUriBuilder.fromUri(ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), client.getBaseUrl())).build();
}
return Response.seeOther(targetUri).build();

View file

@ -33,6 +33,7 @@ import org.keycloak.services.util.CookieHelper;
import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType;
import javax.ws.rs.Consumes;
@ -182,10 +183,9 @@ public class WelcomeResource {
map.put("productNameFull", Version.NAME_FULL);
map.put("properties", theme.getProperties());
map.put("adminUrl", session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path("/admin/").build());
URI uri = Urls.themeRoot(session.getContext().getUri().getBaseUri());
String resourcesPath = uri.getPath() + "/" + theme.getType().toString().toLowerCase() + "/" + theme.getName();
map.put("resourcesPath", resourcesPath);
map.put("resourcesPath", "resources/" + Version.RESOURCES_VERSION + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName());
boolean bootstrap = shouldBootstrap();
map.put("bootstrap", bootstrap);
@ -210,7 +210,7 @@ public class WelcomeResource {
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
.entity(result)
.cacheControl(CacheControlUtil.noCache());
BrowserSecurityHeaderSetup.headers(rb, BrowserSecurityHeaders.defaultHeaders);
BrowserSecurityHeaderSetup.headers(rb);
return rb.build();
} catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);

View file

@ -24,6 +24,7 @@ import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType;
import javax.json.Json;
@ -77,7 +78,7 @@ public class AccountConsole {
@GET
@NoCache
public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException {
public Response getMainPage() throws IOException, FreeMarkerException {
if (!session.getContext().getUri().getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("/").build()).build();
} else {
@ -85,8 +86,8 @@ public class AccountConsole {
URI baseUri = session.getContext().getUri().getBaseUri();
map.put("authUrl", session.getContext().getContextPath());
map.put("baseUrl", session.getContext().getContextPath() + "/realms/" + realm.getName() + "/account");
map.put("authUrl", session.getContext().getUri(UrlType.FRONTEND).getBaseUri().toString());
map.put("baseUrl", session.getContext().getUri(UrlType.FRONTEND).getBaseUriBuilder().replacePath("/realms/" + realm.getName() + "/account").build().toString());
map.put("realm", realm);
map.put("resourceUrl", Urls.themeRoot(baseUri).getPath() + "/account/" + theme.getName());
map.put("resourceVersion", Version.RESOURCES_VERSION);
@ -195,9 +196,9 @@ public class AccountConsole {
ClientModel referrerClient = realm.getClientByClientId(referrer);
if (referrerClient != null) {
if (referrerUri != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, referrerClient);
referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
} else {
referrerUri = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl());
referrerUri = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), referrerClient.getBaseUrl());
}
if (referrerUri != null) {
@ -210,7 +211,7 @@ public class AccountConsole {
} else if (referrerUri != null) {
referrerClient = realm.getClientByClientId(referrer);
if (client != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, referrerClient);
referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
if (referrerUri != null) {
return new String[]{referrer, referrer, referrerUri};

View file

@ -996,9 +996,9 @@ public class AccountFormService extends AbstractSecuredLocalService {
ClientModel referrerClient = realm.getClientByClientId(referrer);
if (referrerClient != null) {
if (referrerUri != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, referrerClient);
referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
} else {
referrerUri = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), referrerClient.getRootUrl(), referrerClient.getBaseUrl());
referrerUri = ResolveRelative.resolveRelativeUri(session, referrerClient.getRootUrl(), referrerClient.getBaseUrl());
}
if (referrerUri != null) {
@ -1010,7 +1010,7 @@ public class AccountFormService extends AbstractSecuredLocalService {
}
} else if (referrerUri != null) {
if (client != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, client);
referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, client);
if (referrerUri != null) {
return new String[]{referrer, referrerUri};

View file

@ -25,6 +25,7 @@ import javax.ws.rs.NotFoundException;
import org.keycloak.Config;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Version;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@ -33,6 +34,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
@ -42,6 +44,7 @@ import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType;
import javax.ws.rs.GET;
@ -51,6 +54,8 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
import java.io.IOException;
import java.net.URI;
@ -169,9 +174,7 @@ public class AdminConsole {
if (consoleApp == null) {
throw new NotFoundException("Could not find admin console client");
}
return new ClientManager(new RealmManager(session)).toInstallationRepresentation(realm, consoleApp, session.getContext().getAuthServerUrl());
}
return new ClientManager(new RealmManager(session)).toInstallationRepresentation(realm, consoleApp, session.getContext().getUri().getBaseUri()); }
/**
* Permission information
@ -255,10 +258,10 @@ public class AdminConsole {
@GET
@NoCache
public Response logout() {
URI redirect = AdminRoot.adminConsoleUrl(session.getContext().getUri()).build(realm.getName());
URI redirect = AdminRoot.adminConsoleUrl(session.getContext().getUri(UrlType.ADMIN)).build(realm.getName());
return Response.status(302).location(
OIDCLoginProtocolService.logoutUrl(session.getContext().getUri()).queryParam("redirect_uri", redirect.toString()).build(realm.getName())
OIDCLoginProtocolService.logoutUrl(session.getContext().getUri(UrlType.ADMIN)).queryParam("redirect_uri", redirect.toString()).build(realm.getName())
).build();
}
@ -274,19 +277,30 @@ public class AdminConsole {
*/
@GET
@NoCache
public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException {
if (!session.getContext().getUri().getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("/").build()).build();
public Response getMainPage() throws IOException, FreeMarkerException {
if (!session.getContext().getUri(UrlType.ADMIN).getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(session.getContext().getUri(UrlType.ADMIN).getRequestUriBuilder().path("/").build()).build();
} else {
Theme theme = AdminRoot.getTheme(session, realm);
Map<String, Object> map = new HashMap<>();
URI baseUri = session.getContext().getUri().getBaseUri();
URI adminBaseUri = session.getContext().getUri(UrlType.ADMIN).getBaseUri();
String adminBaseUrl = adminBaseUri.toString();
if (adminBaseUrl.endsWith("/")) {
adminBaseUrl = adminBaseUrl.substring(0, adminBaseUrl.length() - 1);
}
map.put("authUrl", session.getContext().getContextPath());
map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getName()).getPath());
map.put("resourceUrl", Urls.themeRoot(baseUri).getPath() + "/admin/" + theme.getName());
URI authServerBaseUri = session.getContext().getUri(UrlType.FRONTEND).getBaseUri();
String authServerBaseUrl = authServerBaseUri.toString();
if (authServerBaseUrl.endsWith("/")) {
authServerBaseUrl = authServerBaseUrl.substring(0, authServerBaseUrl.length() - 1);
}
map.put("authServerUrl", authServerBaseUrl);
map.put("authUrl", adminBaseUrl);
map.put("consoleBaseUrl", Urls.adminConsoleRoot(adminBaseUri, realm.getName()).getPath());
map.put("resourceUrl", Urls.themeRoot(adminBaseUri).getPath() + "/admin/" + theme.getName());
map.put("masterRealm", Config.getAdminRealm());
map.put("resourceVersion", Version.RESOURCES_VERSION);
map.put("properties", theme.getProperties());
@ -294,7 +308,16 @@ public class AdminConsole {
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
BrowserSecurityHeaderSetup.Options headerOptions = null;
// Replace CSP if admin is hosted on different URL
if (!adminBaseUri.equals(authServerBaseUri)) {
headerOptions = BrowserSecurityHeaderSetup.Options.create().allowFrameSrc(UriBuilder.fromUri(authServerBaseUri).replacePath("").build().toString()).build();
}
BrowserSecurityHeaderSetup.headers(builder, realm, headerOptions);
return builder.build();
}
}
@ -302,7 +325,7 @@ public class AdminConsole {
@GET
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
public Response getIndexHtmlRedirect() {
return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("../").build()).build();
return Response.status(302).location(session.getContext().getUri(UrlType.ADMIN).getRequestUriBuilder().path("../").build()).build();
}
@GET

View file

@ -38,6 +38,7 @@ import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
@ -100,7 +101,7 @@ public class AdminRoot {
public Response masterRealmAdminConsoleRedirect() {
RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm();
return Response.status(302).location(
session.getContext().getUri().getBaseUriBuilder().path(AdminRoot.class).path(AdminRoot.class, "getAdminConsole").path("/").build(master.getName())
session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path(AdminRoot.class).path(AdminRoot.class, "getAdminConsole").path("/").build(master.getName())
).build();
}

View file

@ -195,7 +195,7 @@ public class ClientResource {
ClientInstallationProvider provider = session.getProvider(ClientInstallationProvider.class, providerId);
if (provider == null) throw new NotFoundException("Unknown Provider");
return provider.generateInstallation(session, realm, client, keycloak.getBaseUri(session.getContext().getUri()));
return provider.generateInstallation(session, realm, client, session.getContext().getUri().getBaseUri());
}
/**
@ -424,7 +424,7 @@ public class ClientResource {
auth.clients().requireConfigure(client);
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).resource(ResourceType.CLIENT).success();
return new ResourceAdminManager(session).pushClientRevocationPolicy(session.getContext().getUri().getRequestUri(), realm, client);
return new ResourceAdminManager(session).pushClientRevocationPolicy(realm, client);
}
@ -598,7 +598,7 @@ public class ClientResource {
auth.clients().requireConfigure(client);
logger.debug("Test availability of cluster nodes");
GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(session.getContext().getUri().getRequestUri(), realm, client);
GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(realm, client);
adminEvent.operation(OperationType.ACTION).resource(ResourceType.CLUSTER_NODE).resourcePath(session.getContext().getUri()).representation(result).success();
return result;
}

View file

@ -551,7 +551,7 @@ public class RealmAdminResource {
public GlobalRequestResult pushRevocation() {
auth.realm().requireManageRealm();
GlobalRequestResult result = new ResourceAdminManager(session).pushRealmRevocationPolicy(session.getContext().getUri().getRequestUri(), realm);
GlobalRequestResult result = new ResourceAdminManager(session).pushRealmRevocationPolicy(realm);
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(result).success();
return result;
}
@ -567,7 +567,7 @@ public class RealmAdminResource {
auth.users().requireManage();
session.sessions().removeUserSessions(realm);
GlobalRequestResult result = new ResourceAdminManager(session).logoutAll(session.getContext().getUri().getRequestUri(), realm);
GlobalRequestResult result = new ResourceAdminManager(session).logoutAll(realm);
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(result).success();
return result;
}

View file

@ -131,7 +131,6 @@ public class RealmsAdminResource {
@Consumes(MediaType.APPLICATION_JSON)
public Response importRealm(final RealmRepresentation rep) {
RealmManager realmManager = new RealmManager(session);
realmManager.setContextPath(keycloak.getContextPath());
AdminPermissions.realms(session, auth).requireCreateRealm();
logger.debugv("importRealm: {0}", rep.getRealm());

View file

@ -699,7 +699,7 @@ public class UserResource {
String redirect;
if (redirectUri != null) {
redirect = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), redirectUri, realm, client);
redirect = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
if (redirect == null) {
throw new WebApplicationException(
ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));

View file

@ -17,6 +17,10 @@
package org.keycloak.services.util;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
@ -25,19 +29,30 @@ import java.net.URI;
* @version $Revision: 1 $
*/
public class ResolveRelative {
public static String resolveRelativeUri(URI requestUri, String rootUrl, String url) {
if (url == null || !url.startsWith("/")) return url;
if (rootUrl != null) {
return rootUrl + url;
} else if (requestUri != null) {
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
builder.scheme(requestUri.getScheme());
if (requestUri.getPort() != -1) {
builder.port(requestUri.getPort());
}
return builder.build().toString();
public static String resolveRelativeUri(KeycloakSession session, String rootUrl, String url) {
if (url == null || !url.startsWith("/")) {
return url;
} else if (rootUrl != null) {
return resolveRootUrl(session, rootUrl) + url;
} else {
return null;
return session.getContext().getUri().getBaseUriBuilder().replacePath(url).build().toString();
}
}
public static String resolveRootUrl(KeycloakSession session, String rootUrl) {
if (rootUrl != null) {
if (rootUrl.equals(Constants.AUTH_BASE_URL_PROP)) {
rootUrl = session.getContext().getUri(UrlType.FRONTEND).getBaseUri().toString();
if (rootUrl.endsWith("/")) {
rootUrl = rootUrl.substring(0, rootUrl.length() - 1);
}
} else if (rootUrl.equals(Constants.AUTH_ADMIN_URL_PROP)) {
rootUrl = session.getContext().getUri(UrlType.ADMIN).getBaseUri().toString();
if (rootUrl.endsWith("/")) {
rootUrl = rootUrl.substring(0, rootUrl.length() - 1);
}
}
}
return rootUrl;
}
}

View file

@ -20,6 +20,7 @@ package org.keycloak.theme;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.RealmModel;
import javax.swing.text.html.Option;
import javax.ws.rs.core.Response;
import java.util.Map;
@ -29,15 +30,50 @@ import java.util.Map;
*/
public class BrowserSecurityHeaderSetup {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
return headers(builder, realm.getBrowserSecurityHeaders());
public static class Options {
private String allowedFrameSrc;
public static Options create() {
return new Options();
}
public Options allowFrameSrc(String source) {
allowedFrameSrc = source;
return this;
}
public Options build() {
return this;
}
}
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, Map<String, String> headers) {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
return headers(builder, realm.getBrowserSecurityHeaders(), null);
}
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm, Options options) {
return headers(builder, realm.getBrowserSecurityHeaders(), options);
}
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder) {
return headers(builder, BrowserSecurityHeaders.defaultHeaders, null);
}
private static Response.ResponseBuilder headers(Response.ResponseBuilder builder, Map<String, String> headers, Options options) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
builder.header(headerName, entry.getValue());
String header = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
String value = entry.getValue();
if (options != null) {
if (header.equals(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY) && value.equals(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY_DEFAULT) && options.allowedFrameSrc != null) {
value = "frame-src " + options.allowedFrameSrc + "; frame-ancestors 'self'; object-src 'none';";
}
}
if (header != null && value != null && !value.isEmpty()) {
builder.header(header, value);
}
}
return builder;

View file

@ -0,0 +1,112 @@
package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.net.URISyntaxException;
public class DefaultHostnameProvider implements HostnameProvider {
private static final Logger LOGGER = Logger.getLogger(DefaultHostnameProvider.class);
private final KeycloakSession session;
private final URI frontendUri;
private String currentRealm;
private URI realmUri;
private URI adminUri;
private final boolean forceBackendUrlToFrontendUrl;
public DefaultHostnameProvider(KeycloakSession session, URI frontendUri, URI adminUri, boolean forceBackendUrlToFrontendUrl) {
this.session = session;
this.frontendUri = frontendUri;
this.adminUri = adminUri;
this.forceBackendUrlToFrontendUrl = forceBackendUrlToFrontendUrl;
}
@Override
public String getScheme(UriInfo originalUriInfo, UrlType type) {
return resolveUri(originalUriInfo, type).getScheme();
}
@Override
public String getHostname(UriInfo originalUriInfo, UrlType type) {
return resolveUri(originalUriInfo, type).getHost();
}
@Override
public int getPort(UriInfo originalUriInfo, UrlType type) {
return resolveUri(originalUriInfo, type).getPort();
}
@Override
public String getContextPath(UriInfo originalUriInfo, UrlType type) {
return resolveUri(originalUriInfo, type).getPath();
}
private URI resolveUri(UriInfo originalUriInfo, UrlType type) {
URI realmUri = getRealmUri();
URI frontendUri = realmUri != null ? realmUri : this.frontendUri;
// Use frontend URI for backend requests if forceBackendUrlToFrontendUrl is true
if (type.equals(UrlType.BACKEND) && forceBackendUrlToFrontendUrl) {
type = UrlType.FRONTEND;
}
// Use frontend URI for backend requests if request hostname matches frontend hostname
if (type.equals(UrlType.BACKEND) && frontendUri != null && originalUriInfo.getBaseUri().getHost().equals(frontendUri.getHost())) {
type = UrlType.FRONTEND;
}
// Use frontend URI for admin requests if adminUrl not set
if (type.equals(UrlType.ADMIN)) {
if (adminUri != null) {
return adminUri;
} else {
type = UrlType.FRONTEND;
}
}
if (type.equals(UrlType.FRONTEND) && frontendUri != null) {
return frontendUri;
}
return originalUriInfo.getBaseUri();
}
private URI getRealmUri() {
RealmModel realm = session.getContext().getRealm();
if (realm == null) {
currentRealm = null;
realmUri = null;
return null;
} else if (realm.getId().equals(currentRealm)) {
return realmUri;
} else {
currentRealm = realm.getId();
realmUri = null;
String realmFrontendUrl = session.getContext().getRealm().getAttribute("frontendUrl");
if (realmFrontendUrl != null) {
try {
realmUri = new URI(realmFrontendUrl);
} catch (URISyntaxException e) {
LOGGER.error("Failed to parse realm frontendUrl. Falling back to global value.", e);
}
}
return realmUri;
}
}
}

View file

@ -0,0 +1,56 @@
package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory;
import java.net.URI;
import java.net.URISyntaxException;
public class DefaultHostnameProviderFactory implements HostnameProviderFactory {
private static final Logger LOGGER = Logger.getLogger(DefaultHostnameProviderFactory.class);
private URI frontendUri;
private URI adminUri;
private boolean forceBackendUrlToFrontendUrl;
@Override
public HostnameProvider create(KeycloakSession session) {
return new DefaultHostnameProvider(session, frontendUri, adminUri, forceBackendUrlToFrontendUrl);
}
@Override
public void init(Config.Scope config) {
String frontendUrl = config.get("frontendUrl");
String adminUrl = config.get("adminUrl");
if (frontendUrl != null && !frontendUrl.isEmpty()) {
try {
frontendUri = new URI(frontendUrl);
} catch (URISyntaxException e) {
throw new RuntimeException("Invalid value for frontendUrl", e);
}
}
if (adminUrl != null && !adminUrl.isEmpty()) {
try {
adminUri = new URI(adminUrl);
} catch (URISyntaxException e) {
throw new RuntimeException("Invalid value for adminUrl", e);
}
}
forceBackendUrlToFrontendUrl = config.getBoolean("forceBackendUrlToFrontendUrl", false);
LOGGER.infov("Frontend: {0}, Admin: {1}, Backend: {2}", frontendUri != null ? frontendUri.toString() : "<request>", adminUri != null ? adminUri.toString() : "<frontend>", forceBackendUrlToFrontendUrl ? "<frontend>" : "<request>");
}
@Override
public String getId() {
return "default";
}
}

View file

@ -6,6 +6,7 @@ import org.keycloak.urls.HostnameProvider;
import javax.ws.rs.core.UriInfo;
@Deprecated
public class FixedHostnameProvider implements HostnameProvider {
private final KeycloakSession session;

View file

@ -1,12 +1,18 @@
package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory;
@Deprecated
public class FixedHostnameProviderFactory implements HostnameProviderFactory {
private static final Logger LOGGER = Logger.getLogger(RequestHostnameProviderFactory.class);
private boolean loggedDeprecatedWarning = false;
private String hostname;
private int httpPort;
private int httpsPort;
@ -14,16 +20,17 @@ public class FixedHostnameProviderFactory implements HostnameProviderFactory {
@Override
public HostnameProvider create(KeycloakSession session) {
if (!loggedDeprecatedWarning) {
loggedDeprecatedWarning = true;
LOGGER.warn("fixed hostname provider is deprecated, please switch to the default hostname provider");
}
return new FixedHostnameProvider(session, alwaysHttps, 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);
this.alwaysHttps = config.getBoolean("alwaysHttps", false);

View file

@ -4,6 +4,7 @@ import org.keycloak.urls.HostnameProvider;
import javax.ws.rs.core.UriInfo;
@Deprecated
public class RequestHostnameProvider implements HostnameProvider {
@Override

View file

@ -1,15 +1,26 @@
package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory;
import javax.ws.rs.core.UriInfo;
@Deprecated
public class RequestHostnameProviderFactory implements HostnameProviderFactory {
private static final Logger LOGGER = Logger.getLogger(RequestHostnameProviderFactory.class);
private boolean loggedDeprecatedWarning = false;
@Override
public HostnameProvider create(KeycloakSession session) {
if (!loggedDeprecatedWarning) {
loggedDeprecatedWarning = true;
LOGGER.warn("request hostname provider is deprecated, please switch to the default hostname provider");
}
return new RequestHostnameProvider();
}

View file

@ -1,2 +1,3 @@
org.keycloak.url.DefaultHostnameProviderFactory
org.keycloak.url.FixedHostnameProviderFactory
org.keycloak.url.RequestHostnameProviderFactory

View file

@ -354,6 +354,7 @@ This test will:
-Djdbc.mvn.artifactId=mysql-connector-java \
-Djdbc.mvn.version=8.0.12 \
-Djdbc.mvn.version.legacy=5.1.38 \
-Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver \
-Dkeycloak.connectionsJpa.url=jdbc:mysql://$DB_HOST/keycloak \
-Dkeycloak.connectionsJpa.user=keycloak \
-Dkeycloak.connectionsJpa.password=keycloak

View file

@ -17,6 +17,8 @@
package org.keycloak.testsuite.updaters;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ComponentResource;
import org.keycloak.admin.client.resource.ComponentsResource;
import org.keycloak.admin.client.resource.GroupResource;
@ -24,6 +26,7 @@ import org.keycloak.admin.client.resource.GroupsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -57,6 +60,16 @@ public class Creator<T> implements AutoCloseable {
}
}
public static Creator<ClientResource> create(RealmResource realmResource, ClientRepresentation rep) {
final ClientsResource clients = realmResource.clients();
try (Response response = clients.create(rep)) {
String createdId = getCreatedId(response);
final ClientResource r = clients.get(createdId);
LOG.debugf("Created client ID %s", createdId);
return new Creator(createdId, r, r::remove);
}
}
public static Creator<UserResource> create(RealmResource realmResource, UserRepresentation rep) {
final UsersResource users = realmResource.users();
try (Response response = users.create(rep)) {

View file

@ -1165,7 +1165,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
"Offline access"
));
Assert.assertThat(accountEntry.getClientScopesGranted(), containsInAnyOrder("Full Access"));
Assert.assertEquals(oauth.AUTH_SERVER_ROOT + "/realms/test/account", accountEntry.getHref());
Assert.assertEquals(oauth.AUTH_SERVER_ROOT + "/realms/test/account/", accountEntry.getHref());
AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
Assert.assertEquals(6, testAppEntry.getRolesAvailable().size());

View file

@ -43,7 +43,7 @@ public class AdminConsoleLandingPageTest extends AbstractKeycloakTest {
String authUrl = body.substring(body.indexOf("var authUrl = '") + 15);
authUrl = authUrl.substring(0, authUrl.indexOf("'"));
Assert.assertEquals("/auth", authUrl);
Assert.assertEquals(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", authUrl);
String resourceUrl = body.substring(body.indexOf("var resourceUrl = '") + 19);
resourceUrl = resourceUrl.substring(0, resourceUrl.indexOf("'"));
@ -67,7 +67,7 @@ public class AdminConsoleLandingPageTest extends AbstractKeycloakTest {
while(m.find()) {
String url = m.group(1);
if (url.contains("keycloak.js")) {
Assert.assertTrue(url, url.startsWith("/auth/js/"));
Assert.assertTrue(url, url.startsWith(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/js/"));
} else {
Assert.assertTrue(url, url.startsWith("/auth/resources/"));
}

View file

@ -131,12 +131,14 @@ public class RealmTest extends AbstractAdminTest {
Assert.assertEquals(1, adminClient.realm("master").clients().findByClientId("new-realm").size());
ClientRepresentation adminConsoleClient = adminClient.realm("new").clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
assertEquals("/auth/admin/new/console/index.html", adminConsoleClient.getBaseUrl());
assertEquals("/auth/admin/new/console/*", adminConsoleClient.getRedirectUris().get(0));
assertEquals(Constants.AUTH_ADMIN_URL_PROP, adminConsoleClient.getRootUrl());
assertEquals("/admin/new/console/", adminConsoleClient.getBaseUrl());
assertEquals("/admin/new/console/*", adminConsoleClient.getRedirectUris().get(0));
ClientRepresentation accountClient = adminClient.realm("new").clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
assertEquals("/auth/realms/new/account", accountClient.getBaseUrl());
assertEquals("/auth/realms/new/account/*", accountClient.getRedirectUris().get(0));
assertEquals(Constants.AUTH_BASE_URL_PROP, accountClient.getRootUrl());
assertEquals("/realms/new/account/", accountClient.getBaseUrl());
assertEquals("/realms/new/account/*", accountClient.getRedirectUris().get(0));
} finally {
adminClient.realms().realm(rep.getRealm()).remove();
}

View file

@ -373,7 +373,7 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
waitForPage(driver, "sorry", false);
errorPage.assertCurrent();
String link = errorPage.getBackToApplicationLink();
Assert.assertTrue(link.endsWith("/auth/realms/consumer/account"));
Assert.assertTrue(link.endsWith("/auth/realms/consumer/account/"));
}
/**

View file

@ -89,7 +89,7 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
AdapterConfig config = reg.getAdapterConfig(client.getClientId());
assertNotNull(config);
assertEquals(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", config.getAuthServerUrl());
assertEquals(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/", config.getAuthServerUrl());
assertEquals("test", config.getRealm());
assertEquals(1, config.getCredentials().size());

View file

@ -75,7 +75,7 @@ public class ClientRedirectTest extends AbstractTestRealmKeycloakTest {
assertEquals("http://example.org/dummy/base-path", driver.getCurrentUrl());
driver.get(getAuthServerRoot().toString() + "realms/test/clients/account/redirect");
assertEquals(getAuthServerRoot().toString() + "realms/test/account", driver.getCurrentUrl());
assertEquals(getAuthServerRoot().toString() + "realms/test/account/", driver.getCurrentUrl());
}
@Test

View file

@ -200,7 +200,7 @@ public class LDAPMultipleAttributesTest extends AbstractLDAPTest {
public void ldapPortalEndToEndTest() {
// Login as bwilson
oauth.clientId("ldap-portal");
oauth.redirectUri("/ldap-portal");
oauth.redirectUri(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/ldap-portal");
loginPage.open();
loginPage.login("bwilson", "Password1");

View file

@ -26,8 +26,10 @@ import org.keycloak.component.PrioritizedComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
@ -55,6 +57,7 @@ import org.keycloak.testsuite.util.OAuthClient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -240,6 +243,35 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMicroprofileJWTScopeAddedToClient();
}
protected void testMigrationTo8_0_0() {
testAdminClientUrls(masterRealm);
testAdminClientUrls(migrationRealm);
testAccountClientUrls(masterRealm);
testAccountClientUrls(migrationRealm);
}
private void testAdminClientUrls(RealmResource realm) {
ClientRepresentation adminConsoleClient = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
assertEquals(Constants.AUTH_ADMIN_URL_PROP, adminConsoleClient.getRootUrl());
String baseUrl = "/admin/" + realm.toRepresentation().getRealm() + "/console/";
assertEquals(baseUrl, adminConsoleClient.getBaseUrl());
assertEquals(baseUrl + "*", adminConsoleClient.getRedirectUris().iterator().next());
assertEquals(1, adminConsoleClient.getRedirectUris().size());
assertEquals("+", adminConsoleClient.getWebOrigins().iterator().next());
assertEquals(1, adminConsoleClient.getWebOrigins().size());
}
private void testAccountClientUrls(RealmResource realm) {
ClientRepresentation accountConsoleClient = realm.clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
assertEquals(Constants.AUTH_BASE_URL_PROP, accountConsoleClient.getRootUrl());
String baseUrl = "/realms/" + realm.toRepresentation().getRealm() + "/account/";
assertEquals(baseUrl, accountConsoleClient.getBaseUrl());
assertEquals(baseUrl + "*", accountConsoleClient.getRedirectUris().iterator().next());
assertEquals(1, accountConsoleClient.getRedirectUris().size());
}
private void testDecisionStrategySetOnResourceServer() {
ClientsResource clients = migrationRealm.clients();
ClientRepresentation clientRepresentation = clients.findByClientId("authz-servlet").get(0);
@ -609,6 +641,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo6_0_0();
}
protected void testMigrationTo8_x() {
testMigrationTo8_0_0();
}
protected void testMigrationTo7_x(boolean supportedAuthzServices) {
if (supportedAuthzServices) {
testDecisionStrategySetOnResourceServer();

View file

@ -76,6 +76,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(false);
testMigrationTo8_x();
}
@Override

View file

@ -69,6 +69,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
}

View file

@ -68,6 +68,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
}

View file

@ -62,6 +62,7 @@ public class JsonFileImport483MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
}

View file

@ -72,6 +72,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
@Test
@ -82,6 +83,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
@Test
@ -93,6 +95,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
}
@Test
@ -105,6 +108,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(false);
testMigrationTo8_x();
}
}

View file

@ -0,0 +1,116 @@
package org.keycloak.testsuite.url;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.logging.Logger;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.wildfly.extras.creaper.core.online.ModelNodeResult;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;
public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
private static final Logger LOGGER = Logger.getLogger(AbstractHostnameTest.class);
@ArquillianResource
protected ContainerController controller;
void reset() throws Exception {
LOGGER.info("Reset hostname config to default");
if (suiteContext.getAuthServerInfo().isUndertow()) {
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
removeProperties("keycloak.hostname.provider",
"keycloak.frontendUrl",
"keycloak.adminUrl",
"keycloak.hostname.default.forceBackendUrlToFrontendUrl",
"keycloak.hostname.fixed.hostname",
"keycloak.hostname.fixed.httpPort",
"keycloak.hostname.fixed.httpsPort",
"keycloak.hostname.fixed.alwaysHttps");
controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isJBossBased()) {
executeCli("/subsystem=keycloak-server/spi=hostname:remove",
"/subsystem=keycloak-server/spi=hostname/:add(default-provider=default)",
"/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => \"${keycloak.frontendUrl:}\",forceBackendUrlToFrontendUrl => \"false\"},enabled=true)");
} else {
throw new RuntimeException("Don't know how to config");
}
reconnectAdminClient();
}
void configureDefault(String frontendUrl, boolean forceBackendUrlToFrontendUrl, String adminUrl) throws Exception {
LOGGER.infov("Configuring default hostname provider: frontendUrl={0}, forceBackendUrlToFrontendUrl={1}, adminUrl={3}", frontendUrl, forceBackendUrlToFrontendUrl, adminUrl);
if (suiteContext.getAuthServerInfo().isUndertow()) {
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
System.setProperty("keycloak.hostname.provider", "default");
System.setProperty("keycloak.frontendUrl", frontendUrl);
if (adminUrl != null){
System.setProperty("keycloak.adminUrl", adminUrl);
}
System.setProperty("keycloak.hostname.default.forceBackendUrlToFrontendUrl", String.valueOf(forceBackendUrlToFrontendUrl));
controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isJBossBased()) {
executeCli("/subsystem=keycloak-server/spi=hostname:remove",
"/subsystem=keycloak-server/spi=hostname/:add(default-provider=default)",
"/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={" +
"frontendUrl => \"" + frontendUrl + "\"" +
",forceBackendUrlToFrontendUrl => \"" + forceBackendUrlToFrontendUrl + "\"" +
(adminUrl != null ? ",adminUrl=\"" + adminUrl + "\"" : "") + "},enabled=true)");
} else {
throw new RuntimeException("Don't know how to config");
}
reconnectAdminClient();
}
void configureFixed(String hostname, int httpPort, int httpsPort, boolean alwaysHttps) throws Exception {
if (suiteContext.getAuthServerInfo().isUndertow()) {
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
System.setProperty("keycloak.hostname.provider", "fixed");
System.setProperty("keycloak.hostname.fixed.hostname", hostname);
System.setProperty("keycloak.hostname.fixed.httpPort", String.valueOf(httpPort));
System.setProperty("keycloak.hostname.fixed.httpsPort", String.valueOf(httpsPort));
System.setProperty("keycloak.hostname.fixed.alwaysHttps", String.valueOf(alwaysHttps));
controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isJBossBased()) {
executeCli("/subsystem=keycloak-server/spi=hostname:remove",
"/subsystem=keycloak-server/spi=hostname/:add(default-provider=fixed)",
"/subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => \"" + hostname + "\",httpPort => \"" + httpPort + "\",httpsPort => \"" + httpsPort + "\",alwaysHttps => \"" + alwaysHttps + "\"},enabled=true)");
} else {
throw new RuntimeException("Don't know how to config");
}
reconnectAdminClient();
}
private void executeCli(String... commands) throws Exception {
OnlineManagementClient client = AuthServerTestEnricher.getManagementClient();
Administration administration = new Administration(client);
LOGGER.debug("Running CLI commands:");
for (String c : commands) {
LOGGER.debug(c);
client.execute(c).assertSuccess();
}
LOGGER.debug("Done");
administration.reload();
client.close();
}
private void removeProperties(String... keys) {
for (String k : keys) {
System.getProperties().remove(k);
}
}
}

View file

@ -0,0 +1,239 @@
package org.keycloak.testsuite.url;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.broker.provider.util.SimpleHttp;
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.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
public class DefaultHostnameTest extends AbstractHostnameTest {
@ArquillianResource
protected ContainerController controller;
private String expectedBackendUrl;
private String globalFrontEndUrl = "https://keycloak.127.0.0.1.nip.io/custom";
private String realmFrontEndUrl = "https://my-realm.127.0.0.1.nip.io";
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation test = RealmBuilder.create().name("test")
.client(ClientBuilder.create().name("direct-grant").clientId("direct-grant").enabled(true).secret("password").directAccessGrants())
.user(UserBuilder.create().username("test-user@localhost").password("password"))
.build();
testRealms.add(test);
RealmRepresentation customHostname = RealmBuilder.create().name("frontendUrl")
.client(ClientBuilder.create().name("direct-grant").clientId("direct-grant").enabled(true).secret("password").directAccessGrants())
.user(UserBuilder.create().username("test-user@localhost").password("password"))
.attribute("frontendUrl", realmFrontEndUrl)
.build();
testRealms.add(customHostname);
}
@BeforeClass
public static void enabled() {
ContainerAssume.assumeNotAuthServerRemote();
}
@Test
public void fixedFrontendUrl() throws Exception {
expectedBackendUrl = AUTH_SERVER_ROOT;
oauth.clientId("direct-grant");
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), AuthServerTestEnricher.getAuthServerContextRoot())) {
assertWellKnown("test", expectedBackendUrl);
configureDefault(globalFrontEndUrl, false, null);
assertWellKnown("test", globalFrontEndUrl);
assertTokenIssuer("test", globalFrontEndUrl);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"test", globalFrontEndUrl);
assertBackendForcedToFrontendWithMatchingHostname("test", globalFrontEndUrl);
assertWelcomePage(globalFrontEndUrl);
assertAdminPage("master", globalFrontEndUrl, globalFrontEndUrl);
assertWellKnown("frontendUrl", realmFrontEndUrl);
assertTokenIssuer("frontendUrl", realmFrontEndUrl);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"frontendUrl", realmFrontEndUrl);
assertBackendForcedToFrontendWithMatchingHostname("frontendUrl", realmFrontEndUrl);
assertAdminPage("frontendUrl", realmFrontEndUrl, realmFrontEndUrl);
} finally {
reset();
}
}
@Test
public void fixedAdminUrl() throws Exception {
expectedBackendUrl = AUTH_SERVER_ROOT;
String adminUrl = "https://admin.127.0.0.1.nip.io/custom-admin";
oauth.clientId("direct-grant");
try {
assertWellKnown("test", expectedBackendUrl);
configureDefault(globalFrontEndUrl, false, adminUrl);
assertWelcomePage(adminUrl);
assertAdminPage("master", globalFrontEndUrl, adminUrl);
assertAdminPage("frontendUrl", realmFrontEndUrl, adminUrl);
} finally {
reset();
}
}
@Test
public void forceBackendUrlToFrontendUrl() throws Exception {
expectedBackendUrl = AUTH_SERVER_ROOT;
oauth.clientId("direct-grant");
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), AuthServerTestEnricher.getAuthServerContextRoot())) {
assertWellKnown("test", expectedBackendUrl);
configureDefault(globalFrontEndUrl, true, null);
expectedBackendUrl = globalFrontEndUrl;
assertWellKnown("test", globalFrontEndUrl);
assertTokenIssuer("test", globalFrontEndUrl);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"test", globalFrontEndUrl);
expectedBackendUrl = realmFrontEndUrl;
assertWellKnown("frontendUrl", realmFrontEndUrl);
assertTokenIssuer("frontendUrl", realmFrontEndUrl);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"frontendUrl", realmFrontEndUrl);
} finally {
reset();
}
}
private void assertInitialAccessTokenFromMasterRealm(Keycloak testAdminClient, String realm, String expectedBaseUrl) throws JWSInputException, ClientRegistrationException {
ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation();
rep.setCount(1);
rep.setExpiration(10000);
ClientInitialAccessPresentation initialAccess = testAdminClient.realm(realm).clientInitialAccess().create(rep);
JsonWebToken token = new JWSInput(initialAccess.getToken()).readJsonContent(JsonWebToken.class);
assertEquals(expectedBaseUrl + "/realms/" + realm, token.getIssuer());
ClientRegistration clientReg = ClientRegistration.create().url(AUTH_SERVER_ROOT, 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(expectedBaseUrl + "/realms/" + realm, registrationToken.getIssuer());
}
private void assertTokenIssuer(String realm, String expectedBaseUrl) throws Exception {
oauth.realm(realm);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
AccessToken token = new JWSInput(tokenResponse.getAccessToken()).readJsonContent(AccessToken.class);
assertEquals(expectedBaseUrl + "/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(expectedBaseUrl + "/realms/" + realm, introspectionNode.get("iss").asText());
}
private void assertWellKnown(String realm, String expectedFrontendUrl) throws URISyntaxException {
OIDCConfigurationRepresentation config = oauth.doWellKnownRequest(realm);
assertEquals(expectedFrontendUrl + "/realms/" + realm, config.getIssuer());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/auth", config.getAuthorizationEndpoint());
assertEquals(expectedBackendUrl + "/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint());
assertEquals(expectedBackendUrl + "/realms/" + realm + "/protocol/openid-connect/userinfo", config.getUserinfoEndpoint());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/logout", config.getLogoutEndpoint());
assertEquals(expectedBackendUrl + "/realms/" + realm + "/protocol/openid-connect/certs", config.getJwksUri());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/login-status-iframe.html", config.getCheckSessionIframe());
assertEquals(expectedBackendUrl + "/realms/" + realm + "/clients-registrations/openid-connect", config.getRegistrationEndpoint());
}
// Test backend is forced to frontend if the request hostname matches the frontend
private void assertBackendForcedToFrontendWithMatchingHostname(String realm, String expectedFrontendUrl) throws URISyntaxException {
String host = new URI(expectedFrontendUrl).getHost();
// Scheme and port doesn't matter as we force based on hostname only, so using http and bind port as we can't make requests on configured frontend URL since reverse proxy is not available
oauth.baseUrl("http://" + host + ":" + System.getProperty("auth.server.http.port") + "/auth");
OIDCConfigurationRepresentation config = oauth.doWellKnownRequest(realm);
assertEquals(expectedFrontendUrl + "/realms/" + realm, config.getIssuer());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/auth", config.getAuthorizationEndpoint());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/userinfo", config.getUserinfoEndpoint());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/logout", config.getLogoutEndpoint());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/certs", config.getJwksUri());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/protocol/openid-connect/login-status-iframe.html", config.getCheckSessionIframe());
assertEquals(expectedFrontendUrl + "/realms/" + realm + "/clients-registrations/openid-connect", config.getRegistrationEndpoint());
oauth.baseUrl(AUTH_SERVER_ROOT);
}
private void assertWelcomePage(String expectedAdminUrl) throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
String welcomePage = SimpleHttp.doGet(AUTH_SERVER_ROOT + "/", client).asString();
assertTrue(welcomePage.contains("<a href=\"" + expectedAdminUrl + "/admin/\">"));
}
}
private void assertAdminPage(String realm, String expectedFrontendUrl, String expectedAdminUrl) throws IOException, URISyntaxException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
String indexPage = SimpleHttp.doGet(AUTH_SERVER_ROOT + "/admin/" + realm +"/console/", client).asString();
assertTrue(indexPage.contains("authServerUrl = '" + expectedFrontendUrl +"'"));
assertTrue(indexPage.contains("authUrl = '" + expectedAdminUrl +"'"));
assertTrue(indexPage.contains("consoleBaseUrl = '" + new URI(expectedAdminUrl).getPath() +"/admin/" + realm + "/console/'"));
assertTrue(indexPage.contains("resourceUrl = '" + new URI(expectedAdminUrl).getPath() +"/resources/"));
}
}
}

View file

@ -2,56 +2,84 @@ 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.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.dom.saml.v2.metadata.EndpointType;
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.protocol.saml.SamlProtocol;
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.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ContainerAssume;
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 org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import java.util.HashMap;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.hamcrest.Matchers;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
public class FixedHostnameTest extends AbstractKeycloakTest {
public class FixedHostnameTest extends AbstractHostnameTest {
@ArquillianResource
protected ContainerController controller;
public static final String SAML_CLIENT_ID = "http://whatever.hostname:8280/app/";
private String authServerUrl;
@Override
public void addTestRealms(List<RealmRepresentation> 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");
RealmRepresentation test = RealmBuilder.create().name("test")
.client(ClientBuilder.create().name("direct-grant").clientId("direct-grant").enabled(true).secret("password").directAccessGrants())
.user(UserBuilder.create().username("test-user@localhost").password("password"))
.build();
testRealms.add(test);
RealmRepresentation customHostname = RealmBuilder.create().name("hostname")
.client(ClientBuilder.create().name("direct-grant").clientId("direct-grant").enabled(true).secret("password").directAccessGrants())
.user(UserBuilder.create().username("test-user@localhost").password("password"))
.attribute("hostname", "custom-domain.127.0.0.1.nip.io")
.build();
testRealms.add(customHostname);
}
@ -69,19 +97,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), AuthServerTestEnricher.getAuthServerContextRoot())) {
assertWellKnown("test", AUTH_SERVER_SCHEME + "://localhost:" + AUTH_SERVER_PORT);
assertSamlIdPDescriptor("test", AUTH_SERVER_SCHEME + "://localhost:" + AUTH_SERVER_PORT);
configureFixedHostname(-1, -1, false);
configureFixed("keycloak.127.0.0.1.nip.io", -1, -1, false);
assertWellKnown("test", AUTH_SERVER_SCHEME + "://keycloak.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertSamlIdPDescriptor("test", AUTH_SERVER_SCHEME + "://keycloak.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertWellKnown("hostname", AUTH_SERVER_SCHEME + "://custom-domain.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertSamlIdPDescriptor("hostname", AUTH_SERVER_SCHEME + "://custom-domain.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertTokenIssuer("test", AUTH_SERVER_SCHEME + "://keycloak.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertTokenIssuer("hostname", AUTH_SERVER_SCHEME + "://custom-domain.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"test", AUTH_SERVER_SCHEME + "://keycloak.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertSamlLogin(testAdminClient,"test", AUTH_SERVER_SCHEME + "://keycloak.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertInitialAccessTokenFromMasterRealm(testAdminClient,"hostname", AUTH_SERVER_SCHEME + "://custom-domain.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
assertSamlLogin(testAdminClient,"hostname", AUTH_SERVER_SCHEME + "://custom-domain.127.0.0.1.nip.io:" + AUTH_SERVER_PORT);
} finally {
clearFixedHostname();
reset();
}
}
@ -95,19 +128,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) {
assertWellKnown("test", "http://localhost:8180");
assertSamlIdPDescriptor("test", "http://localhost:8180");
configureFixedHostname(80, -1, false);
configureFixed("keycloak.127.0.0.1.nip.io", 80, -1, false);
assertWellKnown("test", "http://keycloak.127.0.0.1.nip.io");
assertSamlIdPDescriptor("test", "http://keycloak.127.0.0.1.nip.io");
assertWellKnown("hostname", "http://custom-domain.127.0.0.1.nip.io");
assertSamlIdPDescriptor("hostname", "http://custom-domain.127.0.0.1.nip.io");
assertTokenIssuer("test", "http://keycloak.127.0.0.1.nip.io");
assertTokenIssuer("hostname", "http://custom-domain.127.0.0.1.nip.io");
assertInitialAccessTokenFromMasterRealm(testAdminClient,"test", "http://keycloak.127.0.0.1.nip.io");
assertSamlLogin(testAdminClient,"test", "http://keycloak.127.0.0.1.nip.io");
assertInitialAccessTokenFromMasterRealm(testAdminClient,"hostname", "http://custom-domain.127.0.0.1.nip.io");
assertSamlLogin(testAdminClient,"hostname", "http://custom-domain.127.0.0.1.nip.io");
} finally {
clearFixedHostname();
reset();
}
}
@ -121,19 +159,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) {
assertWellKnown("test", "http://localhost:8180");
assertSamlIdPDescriptor("test", "http://localhost:8180");
configureFixedHostname(-1, 443, true);
configureFixed("keycloak.127.0.0.1.nip.io", -1, 443, true);
assertWellKnown("test", "https://keycloak.127.0.0.1.nip.io");
assertSamlIdPDescriptor("test", "https://keycloak.127.0.0.1.nip.io");
assertWellKnown("hostname", "https://custom-domain.127.0.0.1.nip.io");
assertSamlIdPDescriptor("hostname", "https://custom-domain.127.0.0.1.nip.io");
assertTokenIssuer("test", "https://keycloak.127.0.0.1.nip.io");
assertTokenIssuer("hostname", "https://custom-domain.127.0.0.1.nip.io");
assertInitialAccessTokenFromMasterRealm(testAdminClient, "test", "https://keycloak.127.0.0.1.nip.io");
assertSamlLogin(testAdminClient, "test", "https://keycloak.127.0.0.1.nip.io");
assertInitialAccessTokenFromMasterRealm(testAdminClient, "hostname", "https://custom-domain.127.0.0.1.nip.io");
assertSamlLogin(testAdminClient, "hostname", "https://custom-domain.127.0.0.1.nip.io");
} finally {
clearFixedHostname();
reset();
}
}
@ -178,56 +221,63 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
assertEquals(expectedBaseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint());
}
private void configureFixedHostname(int httpPort, int httpsPort, boolean alwaysHttps) throws Exception {
if (suiteContext.getAuthServerInfo().isUndertow()) {
configureUndertow("fixed", "keycloak.127.0.0.1.nip.io", httpPort, httpsPort, alwaysHttps);
} else if (suiteContext.getAuthServerInfo().isJBossBased()) {
configureWildFly("fixed", "keycloak.127.0.0.1.nip.io", httpPort, httpsPort, alwaysHttps);
} else {
throw new RuntimeException("Don't know how to config");
private void assertSamlIdPDescriptor(String realm, String expectedBaseUrl) throws Exception {
final String realmUrl = expectedBaseUrl + "/auth/realms/" + realm;
final String baseSamlEndpointUrl = realmUrl + "/protocol/saml";
String entityDescriptor = null;
try (
CloseableHttpClient client = HttpClientBuilder.create().build();
CloseableHttpResponse resp = client.execute(new HttpGet(baseSamlEndpointUrl + "/descriptor"))
) {
entityDescriptor = EntityUtils.toString(resp.getEntity(), GeneralConstants.SAML_CHARSET);
Object metadataO = SAMLParser.getInstance().parse(new ByteArrayInputStream(entityDescriptor.getBytes(GeneralConstants.SAML_CHARSET)));
assertThat(metadataO, instanceOf(EntitiesDescriptorType.class));
EntitiesDescriptorType metadata = (EntitiesDescriptorType) metadataO;
assertThat(metadata.getEntityDescriptor(), hasSize(1));
assertThat(metadata.getEntityDescriptor().get(0), instanceOf(EntityDescriptorType.class));
EntityDescriptorType ed = (EntityDescriptorType) metadata.getEntityDescriptor().get(0);
assertThat(ed.getEntityID(), is(realmUrl));
IDPSSODescriptorType idpDescriptor = ed.getChoiceType().get(0).getDescriptors().get(0).getIdpDescriptor();
assertThat(idpDescriptor, notNullValue());
final List<String> locations = idpDescriptor.getSingleSignOnService().stream()
.map(EndpointType::getLocation)
.map(URI::toString)
.collect(Collectors.toList());
assertThat(locations, Matchers.everyItem(is(baseSamlEndpointUrl)));
} catch (Exception e) {
log.errorf("Caught exception while parsing SAML descriptor %s", entityDescriptor);
}
reconnectAdminClient();
}
private void clearFixedHostname() throws Exception {
if (suiteContext.getAuthServerInfo().isUndertow()) {
configureUndertow("request", "localhost", -1, -1,false);
} else if (suiteContext.getAuthServerInfo().isJBossBased()) {
configureWildFly("request", "localhost", -1, -1, false);
} else {
throw new RuntimeException("Don't know how to config");
private void assertSamlLogin(Keycloak testAdminClient, String realm, String expectedBaseUrl) throws Exception {
final String realmUrl = expectedBaseUrl + "/auth/realms/" + realm;
final String baseSamlEndpointUrl = realmUrl + "/protocol/saml";
String entityDescriptor = null;
RealmResource realmResource = testAdminClient.realm(realm);
ClientRepresentation clientRep = ClientBuilder.create()
.protocol(SamlProtocol.LOGIN_PROTOCOL)
.clientId(SAML_CLIENT_ID)
.enabled(true)
.attribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false")
.redirectUris("http://foo.bar/")
.build();
try (Creator<ClientResource> c = Creator.create(realmResource, clientRep);
Creator<UserResource> u = Creator.create(realmResource, UserBuilder.create().username("bicycle").password("race").enabled(true).build())) {
SAMLDocumentHolder samlResponse = new SamlClientBuilder()
.authnRequest(new URI(baseSamlEndpointUrl), SAML_CLIENT_ID, "http://foo.bar/", Binding.POST).build()
.login().user("bicycle", "race").build()
.getSamlResponse(Binding.POST);
assertThat(samlResponse.getSamlObject(), org.keycloak.testsuite.util.Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
ResponseType response = (ResponseType) samlResponse.getSamlObject();
assertThat(response.getAssertions(), hasSize(1));
assertThat(response.getAssertions().get(0).getAssertion().getIssuer().getValue(), is(realmUrl));
} catch (Exception e) {
log.errorf("Caught exception while parsing SAML descriptor %s", entityDescriptor);
}
reconnectAdminClient();
}
private void configureUndertow(String provider, String hostname, int httpPort, int httpsPort, boolean alwaysHttps) {
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
System.setProperty("keycloak.hostname.provider", provider);
System.setProperty("keycloak.hostname.fixed.hostname", hostname);
System.setProperty("keycloak.hostname.fixed.httpPort", String.valueOf(httpPort));
System.setProperty("keycloak.hostname.fixed.httpsPort", String.valueOf(httpsPort));
System.setProperty("keycloak.hostname.fixed.alwaysHttps", String.valueOf(alwaysHttps));
controller.start(suiteContext.getAuthServerInfo().getQualifier());
}
private void configureWildFly(String provider, String hostname, int httpPort, int httpsPort, boolean alwaysHttps) 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 + ")");
client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.alwaysHttps,value=" + alwaysHttps + ")");
administration.reloadIfRequired();
client.close();
}
}

View file

@ -81,6 +81,14 @@ public class RealmBuilder {
return this;
}
public RealmBuilder attribute(String key, String value) {
if (rep.getAttributes() == null) {
rep.setAttributes(new HashMap<>());
}
rep.getAttributes().put(key, value);
return this;
}
public RealmBuilder testMail() {
Map<String, String> config = new HashMap<>();
config.put("from", MailServerConfiguration.FROM);

View file

@ -1,13 +1,19 @@
{
"hostname": {
"provider": "${keycloak.hostname.provider:request}",
"provider": "${keycloak.hostname.provider:default}",
"fixed": {
"hostname": "${keycloak.hostname.fixed.hostname:localhost}",
"httpPort": "${keycloak.hostname.fixed.httpPort:-1}",
"httpsPort": "${keycloak.hostname.fixed.httpsPort:-1}",
"alwaysHttps": "${keycloak.hostname.fixed.alwaysHttps:false}"
},
"default": {
"frontendUrl": "${keycloak.frontendUrl:}",
"adminUrl": "${keycloak.adminUrl:}",
"forceBackendUrlToFrontendUrl": "${keycloak.hostname.default.forceBackendUrlToFrontendUrl:false}"
}
},

View file

@ -346,7 +346,6 @@ public class KeycloakServer {
info("Not importing realm " + rep.getRealm() + " realm already exists");
return;
}
manager.setContextPath("/auth");
RealmModel realm = manager.importRealm(rep);
info("Imported realm " + realm.getName());

View file

@ -1,12 +1,12 @@
{
"hostname": {
"provider": "request",
"provider": "${keycloak.hostname.provider:default}",
"fixed": {
"hostname": "localhost",
"httpPort": "-1",
"httpsPort": "-1"
"default": {
"frontendUrl": "${keycloak.frontendUrl:}",
"adminUrl": "${keycloak.adminUrl:}",
"forceBackendUrlToFrontendUrl": "${keycloak.hostname.default.forceBackendUrlToFrontendUrl:false}"
}
},

View file

@ -16,6 +16,7 @@
</#if>
<script type="text/javascript">
var authServerUrl = '${authServerUrl}';
var authUrl = '${authUrl}';
var consoleBaseUrl = '${consoleBaseUrl}';
var resourceUrl = '${resourceUrl}';

View file

@ -120,6 +120,8 @@ 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.
realm-detail.frontendUrl=Frontend URL
realm-detail.frontendUrl.tooltip=Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.
## KEYCLOAK-7688 Offline Session Max for Offline Token
offline-session-max-limited=Offline Session Max Limited

View file

@ -3221,3 +3221,13 @@ module.directive('kcPassword', function ($compile, Notifications) {
}
}
});
module.filter('resolveClientRootUrl', function() {
return function(input) {
if (!input) {
return;
}
return input.replace("${authBaseUrl}", authServerUrl).replace("${authAdminUrl}", authUrl);
};
});

View file

@ -34,7 +34,7 @@
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
<td translate="{{client.enabled}}"></td>
<td ng-class="{'text-muted': !client.baseUrl}">
<a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
<a href="{{client.rootUrl | resolveClientRootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl | resolveClientRootUrl}}{{client.baseUrl}}</a>
<span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
</td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>

View file

@ -23,6 +23,14 @@
</div>
</div>
<div class="form-group" data-ng-show="serverInfo.listProviderIds('hostname').includes('default')">
<label class="col-md-2 control-label" for="name">{{:: 'realm-detail.frontendUrl' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" id="frontendUrl" name="frontendUrl" data-ng-model="realm.attributes.frontendUrl">
</div>
<kc-tooltip>{{:: 'realm-detail.frontendUrl.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="serverInfo.listProviderIds('hostname').includes('fixed')">
<label class="col-md-2 control-label" for="name">{{:: 'realm-detail.hostname' | translate}}</label>
<div class="col-md-6">

View file

@ -89,7 +89,7 @@
</form>
</#if>
<div class="welcome-primary-link">
<h3><a href="admin/"><img src="welcome-content/user.png">Administration Console <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
<h3><a href="${adminUrl}"><img src="welcome-content/user.png">Administration Console <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
<div class="description">
Centrally manage all aspects of the ${productNameFull} server
</div>

View file

@ -78,12 +78,11 @@ keycloak.server.subsys.default.config=\
<provider name="default" enabled="true"/>\
</spi>\
<spi name="hostname">\
<default-provider>request</default-provider>\
<provider name="fixed" enabled="true">\
<default-provider>default</default-provider>\
<provider name="default" enabled="true">\
<properties>\
<property name="hostname" value="localhost"/>\
<property name="httpPort" value="-1"/>\
<property name="httpsPort" value="-1"/>\
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>\
<property name="forceBackendUrlToFrontendUrl" value="false"/>\
</properties>\
</provider>\
</spi>\

View file

@ -1,7 +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=hostname/:add(default-provider=default)
/subsystem=keycloak-server/spi=hostname/provider=default/:add(properties={frontendUrl => "${keycloak.frontendUrl:}",forceBackendUrlToFrontendUrl => "false"},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