KEYCLOAK-11728 New default hostname provider
Co-authored-by: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
parent
7f1de02ca0
commit
b8881b8ea0
89 changed files with 1396 additions and 373 deletions
|
@ -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 ***
|
||||||
|
|
|
@ -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 ***
|
||||||
|
|
|
@ -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 ***
|
||||||
|
|
|
@ -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 ***
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 + "*"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
7
server-spi/src/main/java/org/keycloak/urls/UrlType.java
Normal file
7
server-spi/src/main/java/org/keycloak/urls/UrlType.java
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package org.keycloak.urls;
|
||||||
|
|
||||||
|
public enum UrlType {
|
||||||
|
|
||||||
|
FRONTEND, BACKEND, ADMIN
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
org.keycloak.url.DefaultHostnameProviderFactory
|
||||||
org.keycloak.url.FixedHostnameProviderFactory
|
org.keycloak.url.FixedHostnameProviderFactory
|
||||||
org.keycloak.url.RequestHostnameProviderFactory
|
org.keycloak.url.RequestHostnameProviderFactory
|
|
@ -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
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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/"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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}';
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>\
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue