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 echo
end-if 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 *** echo *** End Migration of /profile=$clusteredProfile ***

View file

@ -577,4 +577,23 @@ if ((result.time == 100L) && (result.unit == MILLISECONDS)) of /profile=$standal
echo echo
end-if 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 *** echo *** End Migration of /profile=$standaloneProfile ***

View file

@ -748,5 +748,23 @@ if (result == UP) of /subsystem=microprofile-health-smallrye:read-attribute(name
echo echo
end-if 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 *** echo *** End Migration ***

View file

@ -613,4 +613,23 @@ if (result == UP) of /subsystem=microprofile-health-smallrye:read-attribute(name
echo echo
end-if 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 *** 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_2_0;
import org.keycloak.migration.migrators.MigrateTo4_6_0; import org.keycloak.migration.migrators.MigrateTo4_6_0;
import org.keycloak.migration.migrators.MigrateTo6_0_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.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -81,7 +82,8 @@ public class MigrationModelManager {
new MigrateTo4_0_0(), new MigrateTo4_0_0(),
new MigrateTo4_2_0(), new MigrateTo4_2_0(),
new MigrateTo4_6_0(), new MigrateTo4_6_0(),
new MigrateTo6_0_0() new MigrateTo6_0_0(),
new MigrateTo8_0_0()
}; };
public static void migrate(KeycloakSession session) { 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 $ * @version $Revision: 1 $
*/ */
public class BrowserSecurityHeaders { 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> headerAttributeMap;
public static final Map<String, String> defaultHeaders; public static final Map<String, String> defaultHeaders;
static { static {
Map<String, String> headerMap = new HashMap<>(); Map<String, String> headerMap = new HashMap<>();
headerMap.put("xFrameOptions", "X-Frame-Options"); headerMap.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS);
headerMap.put("contentSecurityPolicy", "Content-Security-Policy"); headerMap.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY);
headerMap.put("contentSecurityPolicyReportOnly", "Content-Security-Policy-Report-Only"); headerMap.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY);
headerMap.put("xContentTypeOptions", "X-Content-Type-Options"); headerMap.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS);
headerMap.put("xRobotsTag", "X-Robots-Tag"); headerMap.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG);
headerMap.put("xXSSProtection", "X-XSS-Protection"); headerMap.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION);
headerMap.put("strictTransportSecurity", "Strict-Transport-Security"); headerMap.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY);
Map<String, String> dh = new HashMap<>(); Map<String, String> dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN"); dh.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS_DEFAULT);
dh.put("contentSecurityPolicy", "frame-src 'self'; frame-ancestors 'self'; object-src 'none';"); dh.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY_DEFAULT);
dh.put("contentSecurityPolicyReportOnly", ""); dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT);
dh.put("xContentTypeOptions", "nosniff"); dh.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS_DEFAULT);
dh.put("xRobotsTag", "none"); dh.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG_DEFAULT);
dh.put("xXSSProtection", "1; mode=block"); dh.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION_DEFAULT);
dh.put("strictTransportSecurity", "max-age=31536000; includeSubDomains"); dh.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY_DEFAULT);
defaultHeaders = Collections.unmodifiableMap(dh); defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap); 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 BROKER_SERVICE_CLIENT_ID = "broker";
public static final String REALM_MANAGEMENT_CLIENT_ID = "realm-management"; 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 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"; 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.common.ClientConnection;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.urls.UrlType;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import java.net.URI; import java.net.URI;
@ -33,8 +34,23 @@ public interface KeycloakContext {
String getContextPath(); 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(); 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(); HttpHeaders getRequestHeaders();
<T> T getContextObject(Class<T> clazz); <T> T getContextObject(Class<T> clazz);

View file

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

View file

@ -21,20 +21,99 @@ import org.keycloak.provider.Provider;
import javax.ws.rs.core.UriInfo; 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 { 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 * Returns the URL scheme. If not implemented will get the scheme from the request.
* {@link KeycloakContext#getUri()} as it will in turn call the HostnameProvider resulting in an infinite loop!
* *
* @param originalUriInfo the original UriInfo before hostname is replaced by the HostnameProvider * @param originalUriInfo the original URI
* @return the hostname * @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 @Override
default void close() { 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( TokenUtils.checkThat(
// either redirect URI is not specified or must be valid for the client // either redirect URI is not specified or must be valid for the client
t -> t.getRedirectUri() == null t -> t.getRedirectUri() == null
|| RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), t.getRedirectUri(), || RedirectUtils.verifyRedirectUri(tokenContext.getSession(), t.getRedirectUri(),
tokenContext.getRealm(), tokenContext.getAuthenticationSession().getClient()) != null, tokenContext.getAuthenticationSession().getClient()) != null,
Errors.INVALID_REDIRECT_URI, Errors.INVALID_REDIRECT_URI,
Messages.INVALID_REDIRECT_URI Messages.INVALID_REDIRECT_URI
) )
@ -88,8 +88,7 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
.createInfoPage(); .createInfoPage();
} }
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(), String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getSession(), token.getRedirectUri(), authSession.getClient());
tokenContext.getRealm(), authSession.getClient());
if (redirectUri != null) { if (redirectUri != null) {
authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true"); 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) { private Response createSuccessfulResponse(Object response, KeycloakAuthorizationRequest request) {
return Cors.add(request.getHttpRequest(), Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response)) 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) .allowedMethods(HttpMethod.POST)
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
} }

View file

@ -110,7 +110,6 @@ public class ImportUtils {
} }
RealmManager realmManager = new RealmManager(session); RealmManager realmManager = new RealmManager(session);
realmManager.setContextPath(session.getContext().getContextPath());
realmManager.importRealm(rep, skipUserDependent); realmManager.importRealm(rep, skipUserDependent);
if (System.getProperty(ExportImportConfig.ACTION) != null) { 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.protocol.oidc.TokenManager;
import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import java.util.ArrayList; import java.util.ArrayList;
@ -47,7 +48,6 @@ public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<>(); private List<ApplicationEntry> applications = new LinkedList<>();
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) { public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user); Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
for (ClientModel client : getApplications(session, realm, user)) { for (ClientModel client : getApplications(session, realm, user)) {
@ -90,7 +90,7 @@ public class ApplicationsBean {
additionalGrants.add("${offlineToken}"); 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 { public static class ApplicationEntry {
private KeycloakSession session;
private final List<RoleModel> realmRolesAvailable; private final List<RoleModel> realmRolesAvailable;
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable; private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable;
private final ClientModel client; private final ClientModel client;
private final List<String> clientScopesGranted; private final List<String> clientScopesGranted;
private final List<String> additionalGrants; 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) { ClientModel client, List<String> clientScopesGranted, List<String> additionalGrants) {
this.session = session;
this.realmRolesAvailable = realmRolesAvailable; this.realmRolesAvailable = realmRolesAvailable;
this.resourceRolesAvailable = resourceRolesAvailable; this.resourceRolesAvailable = resourceRolesAvailable;
this.client = client; this.client = client;
@ -170,44 +172,7 @@ public class ApplicationsBean {
} }
public String getEffectiveUrl() { public String getEffectiveUrl() {
String rootUrl = getClient().getRootUrl(); return ResolveRelative.resolveRelativeUri(session, getClient().getRootUrl(), getClient().getBaseUrl());
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;
} }
public ClientModel getClient() { public ClientModel getClient() {

View file

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

View file

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

View file

@ -71,7 +71,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientModel(client); OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientModel(client);
if (config.isUseJwksUrl()) { if (config.isUseJwksUrl()) {
String jwksUrl = config.getJwksUrl(); 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); JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
return JWKSUtils.getKeyWrappersForUse(jwks, keyUse); return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
} else if (keyUse == JWK.Use.SIG) { } else if (keyUse == JWK.Use.SIG) {

View file

@ -58,7 +58,7 @@ public class DockerComposeYamlInstallationProvider implements ClientInstallation
final ZipOutputStream zipOutput = new ZipOutputStream(byteStream); final ZipOutputStream zipOutput = new ZipOutputStream(byteStream);
try { 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) { } catch (final IOException e) {
try { try {
zipOutput.close(); zipOutput.close();

View file

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

View file

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

View file

@ -410,7 +410,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
event.detail(Details.REDIRECT_URI, redirectUriParam); event.detail(Details.REDIRECT_URI, redirectUriParam);
// redirect_uri parameter is required per OpenID Connect, but optional per OAuth2 // 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) { if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI); event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM); 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(); RealmModel realm = session.getContext().getRealm();
ClientModel client = session.realms().getClientByClientId(clientId, realm); ClientModel client = session.realms().getClientByClientId(clientId, realm);
if (client != null && client.isEnabled()) { 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())); validWebOrigins.add(UriUtils.getOrigin(uriInfo.getRequestUri()));
if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) { if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) {
return Response.noContent().build(); return Response.noContent().build();

View file

@ -105,7 +105,7 @@ public class LogoutEndpoint {
String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri; String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
if (redirect != null) { if (redirect != null) {
String validatedUri = RedirectUtils.verifyRealmRedirectUri(session.getContext().getUri(), redirect, realm); String validatedUri = RedirectUtils.verifyRealmRedirectUri(session, redirect);
if (validatedUri == null) { if (validatedUri == null) {
event.event(EventType.LOGOUT); event.event(EventType.LOGOUT);
event.detail(Details.REDIRECT_URI, redirect); 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) { private void logout(UserSessionModel userSession, boolean offline) {

View file

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

View file

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

View file

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

View file

@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.utils;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.HashSet; import java.util.HashSet;
@ -31,14 +32,14 @@ public class WebOriginsUtils {
public static final String INCLUDE_REDIRECTS = "+"; 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<>(); Set<String> origins = new HashSet<>();
if (client.getWebOrigins() != null) { if (client.getWebOrigins() != null) {
origins.addAll(client.getWebOrigins()); origins.addAll(client.getWebOrigins());
} }
if (origins.contains(INCLUDE_REDIRECTS)) { if (origins.contains(INCLUDE_REDIRECTS)) {
origins.remove(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://")) { if (redirectUri.startsWith("http://") || redirectUri.startsWith("https://")) {
origins.add(UriUtils.getOrigin(redirectUri)); 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); 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; String logoutServiceUrl = null;
if (SAML_POST_BINDING.equals(bindingType)) { if (SAML_POST_BINDING.equals(bindingType)) {
logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE); logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
@ -557,7 +557,7 @@ public class SamlProtocol implements LoginProtocol {
logoutServiceUrl = client.getManagementUrl(); logoutServiceUrl = client.getManagementUrl();
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) if (logoutServiceUrl == null || logoutServiceUrl.trim().equals(""))
return null; 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); SamlClient samlClient = new SamlClient(client);
try { try {
boolean postBinding = isLogoutPostBindingForClient(clientSession); 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) { 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()); 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; return null;
@ -672,7 +672,7 @@ public class SamlProtocol implements LoginProtocol {
public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) { public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
String logoutUrl = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING); String logoutUrl = getLogoutServiceUrl(session, client, SAML_POST_BINDING);
if (logoutUrl == null) { if (logoutUrl == null) {
logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId()); logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId());
return; return;

View file

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

View file

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

View file

@ -54,7 +54,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
auth.requireView(client); auth.requireView(client);
ClientManager clientManager = new ClientManager(new RealmManager(session)); 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(); event.client(client.getClientId()).success();
return Response.ok(rep).build(); return Response.ok(rep).build();

View file

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

View file

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

View file

@ -64,19 +64,19 @@ public class ResourceAdminManager {
this.session = session; this.session = session;
} }
public static String resolveUri(URI requestUri, String rootUrl, String uri) { public static String resolveUri(KeycloakSession session, String rootUrl, String uri) {
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, rootUrl, uri); String absoluteURI = ResolveRelative.resolveRelativeUri(session, rootUrl, uri);
return StringPropertyReplacer.replaceProperties(absoluteURI); return StringPropertyReplacer.replaceProperties(absoluteURI);
} }
public static String getManagementUrl(URI requestUri, ClientModel client) { public static String getManagementUrl(KeycloakSession session, ClientModel client) {
String mgmtUrl = client.getManagementUrl(); String mgmtUrl = client.getManagementUrl();
if (mgmtUrl == null || mgmtUrl.equals("")) { if (mgmtUrl == null || mgmtUrl.equals("")) {
return null; 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 // 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); return StringPropertyReplacer.replaceProperties(absoluteURI);
@ -84,8 +84,8 @@ public class ResourceAdminManager {
// For non-cluster setup, return just single configured managementUrls // For non-cluster setup, return just single configured managementUrls
// For cluster setup, return the management Urls corresponding to all registered cluster nodes // For cluster setup, return the management Urls corresponding to all registered cluster nodes
private List<String> getAllManagementUrls(URI requestUri, ClientModel client) { private List<String> getAllManagementUrls(ClientModel client) {
String baseMgmtUrl = getManagementUrl(requestUri, client); String baseMgmtUrl = getManagementUrl(session, client);
if (baseMgmtUrl == null) { if (baseMgmtUrl == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -107,14 +107,14 @@ public class ResourceAdminManager {
return result; 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()); keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user); 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 // Map from "app" to clientSessions for this app
MultivaluedHashMap<String, AuthenticatedClientSessionModel> clientSessions = new MultivaluedHashMap<>(); MultivaluedHashMap<String, AuthenticatedClientSessionModel> clientSessions = new MultivaluedHashMap<>();
for (UserSessionModel userSession : userSessions) { for (UserSessionModel userSession : userSessions) {
@ -128,7 +128,7 @@ public class ResourceAdminManager {
if (entry.getValue().size() == 0) { if (entry.getValue().size() == 0) {
continue; 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) { public boolean logoutClientSession(RealmModel realm, ClientModel resource, AuthenticatedClientSessionModel clientSession) {
return logoutClientSessions(requestUri, realm, resource, Arrays.asList(clientSession)); return logoutClientSessions(realm, resource, Arrays.asList(clientSession));
} }
protected boolean logoutClientSessions(URI requestUri, RealmModel realm, ClientModel resource, List<AuthenticatedClientSessionModel> clientSessions) { protected boolean logoutClientSessions(RealmModel realm, ClientModel resource, List<AuthenticatedClientSessionModel> clientSessions) {
String managementUrl = getManagementUrl(requestUri, resource); String managementUrl = getManagementUrl(session, resource);
if (managementUrl != null) { if (managementUrl != null) {
// Key is host, value is list of http sessions for this host // Key is host, value is list of http sessions for this host
@ -195,27 +195,27 @@ public class ResourceAdminManager {
// Methods for logout all // Methods for logout all
public GlobalRequestResult logoutAll(URI requestUri, RealmModel realm) { public GlobalRequestResult logoutAll(RealmModel realm) {
realm.setNotBefore(Time.currentTime()); realm.setNotBefore(Time.currentTime());
List<ClientModel> resources = realm.getClients(); List<ClientModel> resources = realm.getClients();
logger.debugv("logging out {0} resources ", resources.size()); logger.debugv("logging out {0} resources ", resources.size());
GlobalRequestResult finalResult = new GlobalRequestResult(); GlobalRequestResult finalResult = new GlobalRequestResult();
for (ClientModel resource : resources) { for (ClientModel resource : resources) {
GlobalRequestResult currentResult = logoutClient(requestUri, realm, resource, realm.getNotBefore()); GlobalRequestResult currentResult = logoutClient(realm, resource, realm.getNotBefore());
finalResult.addAll(currentResult); finalResult.addAll(currentResult);
} }
return finalResult; return finalResult;
} }
public GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource) { public GlobalRequestResult logoutClient(RealmModel realm, ClientModel resource) {
resource.setNotBefore(Time.currentTime()); 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) { protected GlobalRequestResult logoutClient(RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, resource); List<String> mgmtUrls = getAllManagementUrls(resource);
if (mgmtUrls.isEmpty()) { if (mgmtUrls.isEmpty()) {
logger.debug("No management URL or no registered cluster nodes for the client " + resource.getClientId()); logger.debug("No management URL or no registered cluster nodes for the client " + resource.getClientId());
return new GlobalRequestResult(); 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(); GlobalRequestResult finalResult = new GlobalRequestResult();
for (ClientModel client : realm.getClients()) { for (ClientModel client : realm.getClients()) {
GlobalRequestResult currentResult = pushRevocationPolicy(requestUri, realm, client, realm.getNotBefore()); GlobalRequestResult currentResult = pushRevocationPolicy(realm, client, realm.getNotBefore());
finalResult.addAll(currentResult); finalResult.addAll(currentResult);
} }
return finalResult; return finalResult;
} }
public GlobalRequestResult pushClientRevocationPolicy(URI requestUri, RealmModel realm, ClientModel client) { public GlobalRequestResult pushClientRevocationPolicy(RealmModel realm, ClientModel client) {
return pushRevocationPolicy(requestUri, realm, client, client.getNotBefore()); return pushRevocationPolicy(realm, client, client.getNotBefore());
} }
protected GlobalRequestResult pushRevocationPolicy(URI requestUri, RealmModel realm, ClientModel resource, int notBefore) { protected GlobalRequestResult pushRevocationPolicy(RealmModel realm, ClientModel resource, int notBefore) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, resource); List<String> mgmtUrls = getAllManagementUrls(resource);
if (mgmtUrls.isEmpty()) { if (mgmtUrls.isEmpty()) {
logger.debugf("No management URL or no registered cluster nodes for the client %s", resource.getClientId()); logger.debugf("No management URL or no registered cluster nodes for the client %s", resource.getClientId());
return new GlobalRequestResult(); return new GlobalRequestResult();
@ -297,8 +297,8 @@ public class ResourceAdminManager {
: loginProtocol.sendPushRevocationPolicyRequest(realm, resource, notBefore, managementUrl); : loginProtocol.sendPushRevocationPolicyRequest(realm, resource, notBefore, managementUrl);
} }
public GlobalRequestResult testNodesAvailability(URI requestUri, RealmModel realm, ClientModel client) { public GlobalRequestResult testNodesAvailability(RealmModel realm, ClientModel client) {
List<String> mgmtUrls = getAllManagementUrls(requestUri, client); List<String> mgmtUrls = getAllManagementUrls(client);
if (mgmtUrls.isEmpty()) { if (mgmtUrls.isEmpty()) {
logger.debug("No management URL or no registered cluster nodes for the application " + client.getClientId()); logger.debug("No management URL or no registered cluster nodes for the application " + client.getClientId());
return new GlobalRequestResult(); 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.CollectionUtil;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils; import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -103,9 +104,9 @@ public class Cors {
return this; return this;
} }
public Cors allowedOrigins(UriInfo uriInfo, ClientModel client) { public Cors allowedOrigins(KeycloakSession session, ClientModel client) {
if (client != null) { if (client != null) {
allowedOrigins = WebOriginsUtils.resolveValidWebOrigins(uriInfo, client); allowedOrigins = WebOriginsUtils.resolveValidWebOrigins(session, client);
} }
return this; return this;
} }

View file

@ -213,7 +213,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING); this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING);
checkRealm(); checkRealm();
ClientModel client = checkClient(clientId); ClientModel client = checkClient(clientId);
redirectUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), redirectUri, realmModel, client); redirectUri = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
if (redirectUri == null) { if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI); event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST); 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) { 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) { 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 Set<Class<?>> classes = new HashSet<Class<?>>();
protected KeycloakSessionFactory sessionFactory; protected KeycloakSessionFactory sessionFactory;
protected String contextPath;
public KeycloakApplication() { public KeycloakApplication() {
@ -123,7 +122,6 @@ public class KeycloakApplication extends Application {
loadConfig(context); loadConfig(context);
this.contextPath = context.getContextPath();
this.sessionFactory = createSessionFactory(); this.sessionFactory = createSessionFactory();
Resteasy.pushDefaultContextObject(KeycloakApplication.class, this); Resteasy.pushDefaultContextObject(KeycloakApplication.class, this);
@ -243,7 +241,7 @@ public class KeycloakApplication extends Application {
} }
if (createMasterRealm) { if (createMasterRealm) {
applianceBootstrap.createMasterRealm(contextPath); applianceBootstrap.createMasterRealm();
} }
session.getTransactionManager().commit(); session.getTransactionManager().commit();
} catch (RuntimeException re) { } 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) { public static void loadConfig(ServletContext context) {
try { try {
JsonNode node = null; JsonNode node = null;
@ -410,7 +394,6 @@ public class KeycloakApplication extends Application {
try { try {
RealmManager manager = new RealmManager(session); RealmManager manager = new RealmManager(session);
manager.setContextPath(getContextPath());
if (rep.getId() != null && manager.getRealm(rep.getId()) != null) { if (rep.getId() != null && manager.getRealm(rep.getId()) != null) {
ServicesLogger.LOGGER.realmExists(rep.getRealm(), from); ServicesLogger.LOGGER.realmExists(rep.getRealm(), from);

View file

@ -222,7 +222,7 @@ public class LoginActionsServiceChecks {
ClientModel client = context.getAuthenticationSession().getClient(); 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); 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())) { if (client.getRootUrl() != null && (client.getBaseUrl() == null || client.getBaseUrl().isEmpty())) {
targetUri = KeycloakUriBuilder.fromUri(client.getRootUrl()).build(); targetUri = KeycloakUriBuilder.fromUri(client.getRootUrl()).build();
} else { } 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(); 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.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerUtil; import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -182,10 +183,9 @@ public class WelcomeResource {
map.put("productNameFull", Version.NAME_FULL); map.put("productNameFull", Version.NAME_FULL);
map.put("properties", theme.getProperties()); 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()); map.put("resourcesPath", "resources/" + Version.RESOURCES_VERSION + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName());
String resourcesPath = uri.getPath() + "/" + theme.getType().toString().toLowerCase() + "/" + theme.getName();
map.put("resourcesPath", resourcesPath);
boolean bootstrap = shouldBootstrap(); boolean bootstrap = shouldBootstrap();
map.put("bootstrap", bootstrap); map.put("bootstrap", bootstrap);
@ -210,7 +210,7 @@ public class WelcomeResource {
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST) ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
.entity(result) .entity(result)
.cacheControl(CacheControlUtil.noCache()); .cacheControl(CacheControlUtil.noCache());
BrowserSecurityHeaderSetup.headers(rb, BrowserSecurityHeaders.defaultHeaders); BrowserSecurityHeaderSetup.headers(rb);
return rb.build(); return rb.build();
} catch (Exception e) { } catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); 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.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import javax.json.Json; import javax.json.Json;
@ -77,7 +78,7 @@ public class AccountConsole {
@GET @GET
@NoCache @NoCache
public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException { public Response getMainPage() throws IOException, FreeMarkerException {
if (!session.getContext().getUri().getRequestUri().getPath().endsWith("/")) { if (!session.getContext().getUri().getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("/").build()).build(); return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("/").build()).build();
} else { } else {
@ -85,8 +86,8 @@ public class AccountConsole {
URI baseUri = session.getContext().getUri().getBaseUri(); URI baseUri = session.getContext().getUri().getBaseUri();
map.put("authUrl", session.getContext().getContextPath()); map.put("authUrl", session.getContext().getUri(UrlType.FRONTEND).getBaseUri().toString());
map.put("baseUrl", session.getContext().getContextPath() + "/realms/" + realm.getName() + "/account"); map.put("baseUrl", session.getContext().getUri(UrlType.FRONTEND).getBaseUriBuilder().replacePath("/realms/" + realm.getName() + "/account").build().toString());
map.put("realm", realm); map.put("realm", realm);
map.put("resourceUrl", Urls.themeRoot(baseUri).getPath() + "/account/" + theme.getName()); map.put("resourceUrl", Urls.themeRoot(baseUri).getPath() + "/account/" + theme.getName());
map.put("resourceVersion", Version.RESOURCES_VERSION); map.put("resourceVersion", Version.RESOURCES_VERSION);
@ -195,9 +196,9 @@ public class AccountConsole {
ClientModel referrerClient = realm.getClientByClientId(referrer); ClientModel referrerClient = realm.getClientByClientId(referrer);
if (referrerClient != null) { if (referrerClient != null) {
if (referrerUri != null) { if (referrerUri != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, referrerClient); referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
} else { } else {
referrerUri = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl()); referrerUri = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), referrerClient.getBaseUrl());
} }
if (referrerUri != null) { if (referrerUri != null) {
@ -210,7 +211,7 @@ public class AccountConsole {
} else if (referrerUri != null) { } else if (referrerUri != null) {
referrerClient = realm.getClientByClientId(referrer); referrerClient = realm.getClientByClientId(referrer);
if (client != null) { if (client != null) {
referrerUri = RedirectUtils.verifyRedirectUri(session.getContext().getUri(), referrerUri, realm, referrerClient); referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
if (referrerUri != null) { if (referrerUri != null) {
return new String[]{referrer, referrer, referrerUri}; return new String[]{referrer, referrer, referrerUri};

View file

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

View file

@ -25,6 +25,7 @@ import javax.ws.rs.NotFoundException;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
@ -33,6 +34,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
@ -42,6 +44,7 @@ import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil; import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import javax.ws.rs.GET; 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.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response; 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 javax.ws.rs.ext.Providers;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -169,9 +174,7 @@ public class AdminConsole {
if (consoleApp == null) { if (consoleApp == null) {
throw new NotFoundException("Could not find admin console client"); 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 * Permission information
@ -255,10 +258,10 @@ public class AdminConsole {
@GET @GET
@NoCache @NoCache
public Response logout() { 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( 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(); ).build();
} }
@ -274,19 +277,30 @@ public class AdminConsole {
*/ */
@GET @GET
@NoCache @NoCache
public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException { public Response getMainPage() throws IOException, FreeMarkerException {
if (!session.getContext().getUri().getRequestUri().getPath().endsWith("/")) { if (!session.getContext().getUri(UrlType.ADMIN).getRequestUri().getPath().endsWith("/")) {
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();
} else { } else {
Theme theme = AdminRoot.getTheme(session, realm); Theme theme = AdminRoot.getTheme(session, realm);
Map<String, Object> map = new HashMap<>(); 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()); URI authServerBaseUri = session.getContext().getUri(UrlType.FRONTEND).getBaseUri();
map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getName()).getPath()); String authServerBaseUrl = authServerBaseUri.toString();
map.put("resourceUrl", Urls.themeRoot(baseUri).getPath() + "/admin/" + theme.getName()); 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("masterRealm", Config.getAdminRealm());
map.put("resourceVersion", Version.RESOURCES_VERSION); map.put("resourceVersion", Version.RESOURCES_VERSION);
map.put("properties", theme.getProperties()); map.put("properties", theme.getProperties());
@ -294,7 +308,16 @@ public class AdminConsole {
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); 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); 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(); return builder.build();
} }
} }
@ -302,7 +325,7 @@ public class AdminConsole {
@GET @GET
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html @Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
public Response getIndexHtmlRedirect() { 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 @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.info.ServerInfoAdminResource;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod; import javax.ws.rs.HttpMethod;
@ -100,7 +101,7 @@ public class AdminRoot {
public Response masterRealmAdminConsoleRedirect() { public Response masterRealmAdminConsoleRedirect() {
RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm(); RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm();
return Response.status(302).location( 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(); ).build();
} }

View file

@ -195,7 +195,7 @@ public class ClientResource {
ClientInstallationProvider provider = session.getProvider(ClientInstallationProvider.class, providerId); ClientInstallationProvider provider = session.getProvider(ClientInstallationProvider.class, providerId);
if (provider == null) throw new NotFoundException("Unknown Provider"); 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); auth.clients().requireConfigure(client);
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).resource(ResourceType.CLIENT).success(); 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); auth.clients().requireConfigure(client);
logger.debug("Test availability of cluster nodes"); 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(); adminEvent.operation(OperationType.ACTION).resource(ResourceType.CLUSTER_NODE).resourcePath(session.getContext().getUri()).representation(result).success();
return result; return result;
} }

View file

@ -551,7 +551,7 @@ public class RealmAdminResource {
public GlobalRequestResult pushRevocation() { public GlobalRequestResult pushRevocation() {
auth.realm().requireManageRealm(); 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(); adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(result).success();
return result; return result;
} }
@ -567,7 +567,7 @@ public class RealmAdminResource {
auth.users().requireManage(); auth.users().requireManage();
session.sessions().removeUserSessions(realm); 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(); adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(result).success();
return result; return result;
} }

View file

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

View file

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

View file

@ -17,6 +17,10 @@
package org.keycloak.services.util; 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 javax.ws.rs.core.UriBuilder;
import java.net.URI; import java.net.URI;
@ -25,19 +29,30 @@ import java.net.URI;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ResolveRelative { public class ResolveRelative {
public static String resolveRelativeUri(URI requestUri, String rootUrl, String url) { public static String resolveRelativeUri(KeycloakSession session, String rootUrl, String url) {
if (url == null || !url.startsWith("/")) return url; if (url == null || !url.startsWith("/")) {
if (rootUrl != null) { return url;
return rootUrl + url; } else if (rootUrl != null) {
} else if (requestUri != null) { return resolveRootUrl(session, rootUrl) + url;
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
builder.scheme(requestUri.getScheme());
if (requestUri.getPort() != -1) {
builder.port(requestUri.getPort());
}
return builder.build().toString();
} else { } 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.BrowserSecurityHeaders;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import javax.swing.text.html.Option;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.Map; import java.util.Map;
@ -29,15 +30,50 @@ import java.util.Map;
*/ */
public class BrowserSecurityHeaderSetup { public class BrowserSecurityHeaderSetup {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) { public static class Options {
return headers(builder, realm.getBrowserSecurityHeaders());
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()) { for (Map.Entry<String, String> entry : headers.entrySet()) {
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey()); String header = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) { String value = entry.getValue();
builder.header(headerName, 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; 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; import javax.ws.rs.core.UriInfo;
@Deprecated
public class FixedHostnameProvider implements HostnameProvider { public class FixedHostnameProvider implements HostnameProvider {
private final KeycloakSession session; private final KeycloakSession session;

View file

@ -1,12 +1,18 @@
package org.keycloak.url; package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider; import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory; import org.keycloak.urls.HostnameProviderFactory;
@Deprecated
public class FixedHostnameProviderFactory implements HostnameProviderFactory { public class FixedHostnameProviderFactory implements HostnameProviderFactory {
private static final Logger LOGGER = Logger.getLogger(RequestHostnameProviderFactory.class);
private boolean loggedDeprecatedWarning = false;
private String hostname; private String hostname;
private int httpPort; private int httpPort;
private int httpsPort; private int httpsPort;
@ -14,16 +20,17 @@ public class FixedHostnameProviderFactory implements HostnameProviderFactory {
@Override @Override
public HostnameProvider create(KeycloakSession session) { 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); return new FixedHostnameProvider(session, alwaysHttps, hostname, httpPort, httpsPort);
} }
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
this.hostname = config.get("hostname"); this.hostname = config.get("hostname");
if (this.hostname == null) {
throw new RuntimeException("hostname not set");
}
this.httpPort = config.getInt("httpPort", -1); this.httpPort = config.getInt("httpPort", -1);
this.httpsPort = config.getInt("httpsPort", -1); this.httpsPort = config.getInt("httpsPort", -1);
this.alwaysHttps = config.getBoolean("alwaysHttps", false); this.alwaysHttps = config.getBoolean("alwaysHttps", false);

View file

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

View file

@ -1,15 +1,26 @@
package org.keycloak.url; package org.keycloak.url;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.HostnameProvider; import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory; import org.keycloak.urls.HostnameProviderFactory;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
@Deprecated
public class RequestHostnameProviderFactory implements HostnameProviderFactory { public class RequestHostnameProviderFactory implements HostnameProviderFactory {
private static final Logger LOGGER = Logger.getLogger(RequestHostnameProviderFactory.class);
private boolean loggedDeprecatedWarning = false;
@Override @Override
public HostnameProvider create(KeycloakSession session) { 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(); return new RequestHostnameProvider();
} }

View file

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

View file

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

View file

@ -17,6 +17,8 @@
package org.keycloak.testsuite.updaters; package org.keycloak.testsuite.updaters;
import org.keycloak.admin.client.Keycloak; 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.ComponentResource;
import org.keycloak.admin.client.resource.ComponentsResource; import org.keycloak.admin.client.resource.ComponentsResource;
import org.keycloak.admin.client.resource.GroupResource; 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.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; 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) { public static Creator<UserResource> create(RealmResource realmResource, UserRepresentation rep) {
final UsersResource users = realmResource.users(); final UsersResource users = realmResource.users();
try (Response response = users.create(rep)) { try (Response response = users.create(rep)) {

View file

@ -1165,7 +1165,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
"Offline access" "Offline access"
)); ));
Assert.assertThat(accountEntry.getClientScopesGranted(), containsInAnyOrder("Full 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"); AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
Assert.assertEquals(6, testAppEntry.getRolesAvailable().size()); 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); String authUrl = body.substring(body.indexOf("var authUrl = '") + 15);
authUrl = authUrl.substring(0, authUrl.indexOf("'")); 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); String resourceUrl = body.substring(body.indexOf("var resourceUrl = '") + 19);
resourceUrl = resourceUrl.substring(0, resourceUrl.indexOf("'")); resourceUrl = resourceUrl.substring(0, resourceUrl.indexOf("'"));
@ -67,7 +67,7 @@ public class AdminConsoleLandingPageTest extends AbstractKeycloakTest {
while(m.find()) { while(m.find()) {
String url = m.group(1); String url = m.group(1);
if (url.contains("keycloak.js")) { if (url.contains("keycloak.js")) {
Assert.assertTrue(url, url.startsWith("/auth/js/")); Assert.assertTrue(url, url.startsWith(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/js/"));
} else { } else {
Assert.assertTrue(url, url.startsWith("/auth/resources/")); 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()); 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); ClientRepresentation adminConsoleClient = adminClient.realm("new").clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
assertEquals("/auth/admin/new/console/index.html", adminConsoleClient.getBaseUrl()); assertEquals(Constants.AUTH_ADMIN_URL_PROP, adminConsoleClient.getRootUrl());
assertEquals("/auth/admin/new/console/*", adminConsoleClient.getRedirectUris().get(0)); 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); ClientRepresentation accountClient = adminClient.realm("new").clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
assertEquals("/auth/realms/new/account", accountClient.getBaseUrl()); assertEquals(Constants.AUTH_BASE_URL_PROP, accountClient.getRootUrl());
assertEquals("/auth/realms/new/account/*", accountClient.getRedirectUris().get(0)); assertEquals("/realms/new/account/", accountClient.getBaseUrl());
assertEquals("/realms/new/account/*", accountClient.getRedirectUris().get(0));
} finally { } finally {
adminClient.realms().realm(rep.getRealm()).remove(); adminClient.realms().realm(rep.getRealm()).remove();
} }

View file

@ -373,7 +373,7 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
waitForPage(driver, "sorry", false); waitForPage(driver, "sorry", false);
errorPage.assertCurrent(); errorPage.assertCurrent();
String link = errorPage.getBackToApplicationLink(); 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()); AdapterConfig config = reg.getAdapterConfig(client.getClientId());
assertNotNull(config); assertNotNull(config);
assertEquals(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", config.getAuthServerUrl()); assertEquals(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/", config.getAuthServerUrl());
assertEquals("test", config.getRealm()); assertEquals("test", config.getRealm());
assertEquals(1, config.getCredentials().size()); 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()); assertEquals("http://example.org/dummy/base-path", driver.getCurrentUrl());
driver.get(getAuthServerRoot().toString() + "realms/test/clients/account/redirect"); 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 @Test

View file

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

View file

@ -26,8 +26,10 @@ import org.keycloak.component.PrioritizedComponentModel;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
@ -55,6 +57,7 @@ import org.keycloak.testsuite.util.OAuthClient;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -240,6 +243,35 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMicroprofileJWTScopeAddedToClient(); 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() { private void testDecisionStrategySetOnResourceServer() {
ClientsResource clients = migrationRealm.clients(); ClientsResource clients = migrationRealm.clients();
ClientRepresentation clientRepresentation = clients.findByClientId("authz-servlet").get(0); ClientRepresentation clientRepresentation = clients.findByClientId("authz-servlet").get(0);
@ -609,6 +641,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo6_0_0(); testMigrationTo6_0_0();
} }
protected void testMigrationTo8_x() {
testMigrationTo8_0_0();
}
protected void testMigrationTo7_x(boolean supportedAuthzServices) { protected void testMigrationTo7_x(boolean supportedAuthzServices) {
if (supportedAuthzServices) { if (supportedAuthzServices) {
testDecisionStrategySetOnResourceServer(); testDecisionStrategySetOnResourceServer();

View file

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

View file

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

View file

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

View file

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

View file

@ -72,6 +72,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x(); testMigrationTo5_x();
testMigrationTo6_x(); testMigrationTo6_x();
testMigrationTo7_x(true); testMigrationTo7_x(true);
testMigrationTo8_x();
} }
@Test @Test
@ -82,6 +83,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x(); testMigrationTo5_x();
testMigrationTo6_x(); testMigrationTo6_x();
testMigrationTo7_x(true); testMigrationTo7_x(true);
testMigrationTo8_x();
} }
@Test @Test
@ -93,6 +95,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x(); testMigrationTo5_x();
testMigrationTo6_x(); testMigrationTo6_x();
testMigrationTo7_x(true); testMigrationTo7_x(true);
testMigrationTo8_x();
} }
@Test @Test
@ -105,6 +108,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo5_x(); testMigrationTo5_x();
testMigrationTo6_x(); testMigrationTo6_x();
testMigrationTo7_x(false); 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.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; 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.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.Keycloak; 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.Auth;
import org.keycloak.client.registration.ClientRegistration; import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException; 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.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; 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.AccessToken;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; 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.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ContainerAssume; import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient; import org.keycloak.testsuite.util.RealmBuilder;
import org.wildfly.extras.creaper.core.online.operations.admin.Administration; 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.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.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; 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 public static final String SAML_CLIENT_ID = "http://whatever.hostname:8280/app/";
protected ContainerController controller;
private String authServerUrl; private String authServerUrl;
@Override @Override
public void addTestRealms(List<RealmRepresentation> testRealms) { public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); RealmRepresentation test = RealmBuilder.create().name("test")
testRealms.add(realm); .client(ClientBuilder.create().name("direct-grant").clientId("direct-grant").enabled(true).secret("password").directAccessGrants())
.user(UserBuilder.create().username("test-user@localhost").password("password"))
RealmRepresentation customHostname = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); .build();
customHostname.setId("hostname"); testRealms.add(test);
customHostname.setRealm("hostname");
customHostname.setAttributes(new HashMap<>());
customHostname.getAttributes().put("hostname", "custom-domain.127.0.0.1.nip.io");
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); testRealms.add(customHostname);
} }
@ -69,19 +97,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), AuthServerTestEnricher.getAuthServerContextRoot())) { try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), AuthServerTestEnricher.getAuthServerContextRoot())) {
assertWellKnown("test", AUTH_SERVER_SCHEME + "://localhost:" + AUTH_SERVER_PORT); 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); 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); 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("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); 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); 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); 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 { } finally {
clearFixedHostname(); reset();
} }
} }
@ -95,19 +128,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) { try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) {
assertWellKnown("test", "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"); 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"); 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("test", "http://keycloak.127.0.0.1.nip.io");
assertTokenIssuer("hostname", "http://custom-domain.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"); 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"); 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 { } finally {
clearFixedHostname(); reset();
} }
} }
@ -121,19 +159,24 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) { try (Keycloak testAdminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), "http://localhost:8180")) {
assertWellKnown("test", "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"); 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"); 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("test", "https://keycloak.127.0.0.1.nip.io");
assertTokenIssuer("hostname", "https://custom-domain.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"); 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"); 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 { } finally {
clearFixedHostname(); reset();
} }
} }
@ -178,56 +221,63 @@ public class FixedHostnameTest extends AbstractKeycloakTest {
assertEquals(expectedBaseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint()); assertEquals(expectedBaseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint());
} }
private void configureFixedHostname(int httpPort, int httpsPort, boolean alwaysHttps) throws Exception { private void assertSamlIdPDescriptor(String realm, String expectedBaseUrl) throws Exception {
if (suiteContext.getAuthServerInfo().isUndertow()) { final String realmUrl = expectedBaseUrl + "/auth/realms/" + realm;
configureUndertow("fixed", "keycloak.127.0.0.1.nip.io", httpPort, httpsPort, alwaysHttps); final String baseSamlEndpointUrl = realmUrl + "/protocol/saml";
} else if (suiteContext.getAuthServerInfo().isJBossBased()) { String entityDescriptor = null;
configureWildFly("fixed", "keycloak.127.0.0.1.nip.io", httpPort, httpsPort, alwaysHttps); try (
} else { CloseableHttpClient client = HttpClientBuilder.create().build();
throw new RuntimeException("Don't know how to config"); 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 { private void assertSamlLogin(Keycloak testAdminClient, String realm, String expectedBaseUrl) throws Exception {
if (suiteContext.getAuthServerInfo().isUndertow()) { final String realmUrl = expectedBaseUrl + "/auth/realms/" + realm;
configureUndertow("request", "localhost", -1, -1,false); final String baseSamlEndpointUrl = realmUrl + "/protocol/saml";
} else if (suiteContext.getAuthServerInfo().isJBossBased()) { String entityDescriptor = null;
configureWildFly("request", "localhost", -1, -1, false); RealmResource realmResource = testAdminClient.realm(realm);
} else { ClientRepresentation clientRep = ClientBuilder.create()
throw new RuntimeException("Don't know how to config"); .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; 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() { public RealmBuilder testMail() {
Map<String, String> config = new HashMap<>(); Map<String, String> config = new HashMap<>();
config.put("from", MailServerConfiguration.FROM); config.put("from", MailServerConfiguration.FROM);

View file

@ -1,13 +1,19 @@
{ {
"hostname": { "hostname": {
"provider": "${keycloak.hostname.provider:request}", "provider": "${keycloak.hostname.provider:default}",
"fixed": { "fixed": {
"hostname": "${keycloak.hostname.fixed.hostname:localhost}", "hostname": "${keycloak.hostname.fixed.hostname:localhost}",
"httpPort": "${keycloak.hostname.fixed.httpPort:-1}", "httpPort": "${keycloak.hostname.fixed.httpPort:-1}",
"httpsPort": "${keycloak.hostname.fixed.httpsPort:-1}", "httpsPort": "${keycloak.hostname.fixed.httpsPort:-1}",
"alwaysHttps": "${keycloak.hostname.fixed.alwaysHttps:false}" "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"); info("Not importing realm " + rep.getRealm() + " realm already exists");
return; return;
} }
manager.setContextPath("/auth");
RealmModel realm = manager.importRealm(rep); RealmModel realm = manager.importRealm(rep);
info("Imported realm " + realm.getName()); info("Imported realm " + realm.getName());

View file

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

View file

@ -16,6 +16,7 @@
</#if> </#if>
<script type="text/javascript"> <script type="text/javascript">
var authServerUrl = '${authServerUrl}';
var authUrl = '${authUrl}'; var authUrl = '${authUrl}';
var consoleBaseUrl = '${consoleBaseUrl}'; var consoleBaseUrl = '${consoleBaseUrl}';
var resourceUrl = '${resourceUrl}'; 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. 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=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.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 ## KEYCLOAK-7688 Offline Session Max for Offline Token
offline-session-max-limited=Offline Session Max Limited 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><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
<td translate="{{client.enabled}}"></td> <td translate="{{client.enabled}}"></td>
<td ng-class="{'text-muted': !client.baseUrl}"> <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> <span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
</td> </td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</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> </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')"> <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> <label class="col-md-2 control-label" for="name">{{:: 'realm-detail.hostname' | translate}}</label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -89,7 +89,7 @@
</form> </form>
</#if> </#if>
<div class="welcome-primary-link"> <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"> <div class="description">
Centrally manage all aspects of the ${productNameFull} server Centrally manage all aspects of the ${productNameFull} server
</div> </div>

View file

@ -78,12 +78,11 @@ keycloak.server.subsys.default.config=\
<provider name="default" enabled="true"/>\ <provider name="default" enabled="true"/>\
</spi>\ </spi>\
<spi name="hostname">\ <spi name="hostname">\
<default-provider>request</default-provider>\ <default-provider>default</default-provider>\
<provider name="fixed" enabled="true">\ <provider name="default" enabled="true">\
<properties>\ <properties>\
<property name="hostname" value="localhost"/>\ <property name="frontendUrl" value="${keycloak.frontendUrl:}"/>\
<property name="httpPort" value="-1"/>\ <property name="forceBackendUrlToFrontendUrl" value="false"/>\
<property name="httpsPort" value="-1"/>\
</properties>\ </properties>\
</provider>\ </provider>\
</spi>\ </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: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/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/:add(default-provider=default)
/subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "localhost",httpPort => "-1",httpsPort => "-1"},enabled=true) /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/:add
/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) /subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true)
/subsystem=keycloak-server/spi=userCache/:add /subsystem=keycloak-server/spi=userCache/:add