dbeaver/pro#4404 dynamic server url (#3379)

* dbeaver/pro#4404 wip

* dbeaver/pro#4404 get origin from user request

* dbeaver/pro#4404 dynamic server url

* dbeaver/pro#4404 dynamic server url

* dbeaver/pro#4404 update after merge

* dbeaver/pro#4404 fixes after devel changes

* dbeaver/pro#4404 get origin url from headers

* dbeaver/pro#4404 fix referer

* dbeaver/pro#4404 support forward headers

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
This commit is contained in:
Alexander Skoblikov
2025-06-16 14:51:02 +02:00
committed by GitHub
co-authored by kseniaguzeeva
parent d69eb04b11
commit 6d70e03a9e
21 changed files with 280 additions and 91 deletions
+1
View File
@@ -1,6 +1,7 @@
{
server: {
serverPort: "${CLOUDBEAVER_WEB_SERVER_PORT:8978}",
secureCookies: "${CLOUDBEAVER_SECURE_COOKIES:true}",
contentRoot: "web",
driversLocation: "drivers",
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,27 +30,50 @@ import java.util.Map;
public interface SMAuthProviderFederated extends SMSignOutLinkProvider {
@NotNull
String getSignInLink(String id) throws DBException;
String getSignInLink(@NotNull String id, @NotNull String origin) throws DBException;
@Override
default String getUserSignOutLink(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials
@NotNull Map<String, Object> userCredentials,
@NotNull String origin
) throws DBException {
return getCommonSignOutLink(providerConfig.getId(), providerConfig.getParameters());
return getCommonSignOutLink(providerConfig.getId(), providerConfig.getParameters(), origin);
}
@Nullable
String getMetadataLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
default String getMetadataLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException {
return null;
}
@Nullable
String getAcsLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
default String getAcsLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException {
return null;
}
@Nullable
String getEntityIdLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
default String getEntityIdLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException {
return null;
}
@Nullable
default String getRedirectLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException {
default String getRedirectLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException {
return null;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +28,15 @@ public interface SMSignOutLinkProvider {
* @return a common link for logout, not related with the user context
*/
@NotNull
String getCommonSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
String getCommonSignOutLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException;
String getUserSignOutLink(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials
@NotNull Map<String, Object> userCredentials,
@NotNull String origin
) throws DBException;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,15 @@
package io.cloudbeaver.model.app;
import io.cloudbeaver.auth.CBAuthConstants;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
public interface ServletAuthApplication extends ServletApplication {
ServletAuthConfiguration getAuthConfiguration();
String getAuthServiceURL();
@Nullable
String getAuthServiceUriSegment();
default long getMaxSessionIdleTime() {
return CBAuthConstants.MAX_SESSION_IDLE_TIME;
@@ -32,4 +35,8 @@ public interface ServletAuthApplication extends ServletApplication {
void flushConfiguration() throws DBException;
String getDefaultAuthRole();
default String modifyOrigin(@NotNull String origin) {
return origin;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
@@ -35,12 +36,21 @@ public class WebAuthProviderConfiguration {
private static final Log log = Log.getLog(WebAuthProviderConfiguration.class);
@NotNull
private final WebAuthProviderDescriptor providerDescriptor;
@NotNull
private final SMAuthProviderCustomConfiguration config;
@NotNull
private final String origin;
public WebAuthProviderConfiguration(WebAuthProviderDescriptor providerDescriptor, SMAuthProviderCustomConfiguration config) {
public WebAuthProviderConfiguration(
@NotNull WebAuthProviderDescriptor providerDescriptor,
@NotNull SMAuthProviderCustomConfiguration config,
@NotNull String origin
) {
this.providerDescriptor = providerDescriptor;
this.config = config;
this.origin = origin;
}
public String getProviderId() {
@@ -75,19 +85,19 @@ public class WebAuthProviderConfiguration {
public String getSignInLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated smAuthProviderFederated ?
buildRedirectUrl(smAuthProviderFederated.getSignInLink(getId()))
buildRedirectUrl(smAuthProviderFederated.getSignInLink(getId(), origin), origin)
: null;
}
private String buildRedirectUrl(String baseUrl) {
return baseUrl + "?" + CBAuthConstants.CB_REDIRECT_URL_REQUEST_PARAM + "=" + ServletAppUtils.getFullServerUrl();
private String buildRedirectUrl(@NotNull String baseUrl, @NotNull String origin) {
return baseUrl + "?" + CBAuthConstants.CB_REDIRECT_URL_REQUEST_PARAM + "=" + origin;
}
@Property
public String getSignOutLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMSignOutLinkProvider smSignOutLinkProvider
? smSignOutLinkProvider.getCommonSignOutLink(getId(), config.getParameters())
? smSignOutLinkProvider.getCommonSignOutLink(getId(), config.getParameters(), origin)
: null;
}
@@ -95,7 +105,7 @@ public class WebAuthProviderConfiguration {
public String getRedirectLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated smAuthProviderFederated
? smAuthProviderFederated.getRedirectLink(getId(), config.getParameters())
? smAuthProviderFederated.getRedirectLink(getId(), config.getParameters(), origin)
: null;
}
@@ -103,7 +113,7 @@ public class WebAuthProviderConfiguration {
public String getMetadataLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated smAuthProviderFederated
? smAuthProviderFederated.getMetadataLink(getId(), config.getParameters())
? smAuthProviderFederated.getMetadataLink(getId(), config.getParameters(), origin)
: null;
}
@@ -111,7 +121,7 @@ public class WebAuthProviderConfiguration {
public String getAcsLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated smAuthProviderFederated
? smAuthProviderFederated.getAcsLink(getId(), config.getParameters())
? smAuthProviderFederated.getAcsLink(getId(), config.getParameters(), origin)
: null;
}
@@ -119,7 +129,7 @@ public class WebAuthProviderConfiguration {
public String getEntityIdLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated smAuthProviderFederated
? smAuthProviderFederated.getEntityIdLink(getId(), config.getParameters())
? smAuthProviderFederated.getEntityIdLink(getId(), config.getParameters(), origin)
: null;
}
@@ -32,6 +32,7 @@ public class CBConstants {
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_HOST = "serverHost";
public static final String PARAM_SERVER_NAME = "serverName";
public static final String PARAM_SECURE_COOKIES = "secureCookies";
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
public static final String PARAM_CONTENT_ROOT = "contentRoot";
public static final String PARAM_SERVER_URL = "serverURL";
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,12 +36,17 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.net.URI;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ServletAppUtils {
private static final String HEADER_ORIGIN = "Origin";
private static final String HEADER_REFERER = "Referer";
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
private static final Log log = Log.getLog(ServletAppUtils.class);
public static String getRelativePath(String path, String curDir) {
@@ -168,16 +173,24 @@ public class ServletAppUtils {
}
@NotNull
public static StringBuilder getAuthApiPrefix(String serviceId) throws DBException {
return getAuthApiPrefix(getAuthApplication(), serviceId);
public static StringBuilder getAuthApiUri(@NotNull String serviceId, @NotNull String origin) throws DBException {
return getAuthApiUri(getAuthApplication(), serviceId, origin);
}
@NotNull
public static StringBuilder getAuthApiPrefix(ServletAuthApplication webAuthApplication, String serviceId) {
String authUrl = removeSideSlashes(webAuthApplication.getAuthServiceURL());
StringBuilder apiPrefix = new StringBuilder(authUrl);
apiPrefix.append("/").append(serviceId).append("/");
return apiPrefix;
public static StringBuilder getAuthApiUri(
@NotNull ServletAuthApplication webAuthApplication,
@NotNull String serviceId,
@NotNull String origin
) throws DBException {
String finalOrigin = webAuthApplication.modifyOrigin(origin);
StringBuilder authUriBuilder = new StringBuilder(removeSideSlashes(finalOrigin));
String serviceUriSegment = removeSideSlashes(webAuthApplication.getAuthServiceUriSegment());
if (CommonUtils.isNotEmpty(serviceUriSegment)) {
authUriBuilder.append("/").append(serviceUriSegment);
}
authUriBuilder.append("/").append(serviceId).append("/");
return authUriBuilder;
}
public static void addResponseCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, long maxSessionIdleTime) {
@@ -272,12 +285,46 @@ public class ServletAppUtils {
}
@NotNull
public static String getFullServerUrl() {
ServletApplication application = ServletAppUtils.getServletApplication();
return Stream.of(application.getServerURL(), application.getRootURI())
.map(ServletAppUtils::removeSideSlashes)
.filter(CommonUtils::isNotEmpty)
.collect(Collectors.joining("/"));
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
String origin = request.getHeader(HEADER_ORIGIN);
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_REFERER);
}
String forwardedScheme = request.getHeader(HEADER_FORWARDED_SCHEME);
String forwardedHost = request.getHeader(HEADER_FORWARDED_HOST);
if (CommonUtils.isNotEmpty(forwardedScheme) && CommonUtils.isNotEmpty(forwardedHost)) {
origin = forwardedHost + "://" + forwardedScheme;
}
if (CommonUtils.isEmpty(origin)) {
URI requestUrl = URI.create(request.getRequestURL().toString());
origin = getRootUrlFromUri(requestUrl) + "/";
}
origin = removeSideSlashes(origin);
var app = ServletAppUtils.getServletApplication();
String rootUri = removeSideSlashes(app.getRootURI());
if (!origin.endsWith(rootUri)) {
origin = origin + "/" + rootUri + "/";
}
return removeSideSlashes(origin);
}
public static String getRootUrlFromUri(@NotNull URI uri) {
var builder = new StringBuilder()
.append(uri.getScheme())
.append("://")
.append(uri.getHost());
if (uri.getPort() > 0) {
builder.append(":").append(uri.getPort());
}
return removeSideSlashes(substringBeforeRootURI(builder.toString()));
}
public static String substringBeforeRootURI(@NotNull String uri) {
String rootUri = removeSideSlashes(getServletApplication().getRootURI());
if (CommonUtils.isEmpty(rootUri)) {
return uri;
}
String[] split = uri.split(rootUri);
return split[0];
}
}
@@ -31,6 +31,7 @@ public class CBServerConfig implements WebServerConfiguration {
private static final Log log = Log.getLog(CBServerConfig.class);
protected String serverURL;
protected boolean secureCookies;
protected int serverPort = CBConstants.DEFAULT_SERVER_PORT;
private String serverHost = null;
private String serverName = null;
@@ -181,4 +182,8 @@ public class CBServerConfig implements WebServerConfiguration {
protected SMControllerConfiguration createSecurityManagerConfiguration() {
return new SMControllerConfiguration();
}
public boolean isSecureCookies() {
return secureCookies;
}
}
@@ -184,7 +184,7 @@ public abstract class CBApplication<T extends CBServerConfig>
}
@Override
public String getAuthServiceURL() {
public String getAuthServiceUriSegment() {
return getServerConfigurationController().getAuthServiceURL();
}
@@ -74,12 +74,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
}
public String getAuthServiceURL() {
return Stream.of(serverConfiguration.getServerURL(),
serverConfiguration.getRootURI(),
serverConfiguration.getServicesURI())
.map(ServletAppUtils::removeSideSlashes)
.filter(CommonUtils::isNotEmpty)
.collect(Collectors.joining("/"));
return serverConfiguration.getServicesURI();
}
@Override
@@ -533,6 +528,12 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
CBConstants.PARAM_SESSION_EXPIRE_PERIOD,
serverConfig.getMaxSessionIdleTime());
}
copyConfigValue(
originServerConfig,
serverConfigProperties,
CBConstants.PARAM_SECURE_COOKIES,
serverConfig.isSecureCookies()
);
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
CBConstants.PARAM_PRODUCT_SETTINGS);
@@ -231,8 +231,7 @@ public class CBJettyServer {
&& servletContextHandler.getSessionHandler() instanceof CBSessionHandler cbSessionHandler
) {
cbSessionHandler.setMaxCookieAge((int) (application.getMaxSessionIdleTime() / 1000));
var serverUrl = this.application.getServerURL();
cbSessionHandler.setSecureCookies(serverUrl != null && serverUrl.startsWith("https://"));
cbSessionHandler.setSecureCookies(application.getServerConfiguration().isSecureCookies());
}
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,9 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletApiRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Request;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.utils.CommonUtils;
@@ -141,6 +144,13 @@ public class GraphQLEndpoint extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String contentType = request.getContentType();
if (request instanceof ServletApiRequest apiRequest) {
ServletContextHandler.ServletRequestInfo info = apiRequest.getServletRequestInfo();
if (info instanceof Request.Wrapper wrapper) {
System.out.println(wrapper);
}
}
System.out.println(request.toString());
if (CommonUtils.isEmpty(contentType) || !contentType.startsWith(HttpConstants.TYPE_JSON)) {
String error = "Bad request," + (CommonUtils.isEmpty(contentType)
? " content type is missing"
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebAuthProviderConfiguration;
import io.cloudbeaver.service.DBWService;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
@@ -119,10 +120,11 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<WebPropertyInfo> listAuthProviderConfigurationParameters(@NotNull WebSession webSession, @NotNull String providerId) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<WebAuthProviderConfiguration> listAuthProviderConfigurations(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException;
List<WebAuthProviderConfiguration> listAuthProviderConfigurations(@NotNull HttpServletRequest request, @NotNull WebSession webSession, @Nullable String providerId) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebAuthProviderConfiguration saveAuthProviderConfiguration(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull String providerId,
@NotNull String id,
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package io.cloudbeaver.service.admin;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceBindingServlet;
import io.cloudbeaver.service.DBWServletContext;
@@ -152,17 +153,20 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
.dataFetcher("listAuthProviderConfigurationParameters",
env -> getService(env).listAuthProviderConfigurationParameters(getWebSession(env), env.getArgument("providerId")))
.dataFetcher("listAuthProviderConfigurations",
env -> getService(env).listAuthProviderConfigurations(getWebSession(env), env.getArgument("providerId")))
env -> getService(env).listAuthProviderConfigurations(GraphQLEndpoint.getServletRequest(env),
getWebSession(env), env.getArgument("providerId"))
)
.dataFetcher("saveAuthProviderConfiguration",
env -> getService(env).saveAuthProviderConfiguration(
GraphQLEndpoint.getServletRequest(env),
getWebSession(env),
env.getArgument("providerId"),
env.getArgument("id"),
env.getArgument("displayName"),
CommonUtils.toBoolean((Boolean)env.getArgument("disabled")),
env.getArgument("iconURL"),
env.getArgument("description"),
env.getArgument("parameters")))
env.getArgument("description"), env.getArgument("parameters")
))
.dataFetcher("deleteAuthProviderConfiguration",
env -> getService(env).deleteAuthProviderConfiguration(getWebSession(env), env.getArgument("id")))
@@ -35,6 +35,7 @@ import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.admin.*;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -488,7 +489,12 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
@Override
public List<WebAuthProviderConfiguration> listAuthProviderConfigurations(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException {
public List<WebAuthProviderConfiguration> listAuthProviderConfigurations(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@Nullable String providerId
) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
List<WebAuthProviderConfiguration> result = new ArrayList<>();
for (SMAuthProviderCustomConfiguration cfg : CBApplication.getInstance().getAppConfiguration().getAuthCustomConfigurations()) {
if (providerId != null && !providerId.equals(cfg.getProvider())) {
@@ -496,7 +502,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(cfg.getProvider());
if (authProvider != null) {
result.add(new WebAuthProviderConfiguration(authProvider, cfg));
result.add(new WebAuthProviderConfiguration(authProvider, cfg, origin));
}
}
return result;
@@ -504,6 +510,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@Override
public WebAuthProviderConfiguration saveAuthProviderConfiguration(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull String providerId,
@NotNull String id,
@@ -537,7 +544,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
providerConfig.getProvider(),
webSession.getUserId()
));
return new WebAuthProviderConfiguration(authProvider, providerConfig);
return new WebAuthProviderConfiguration(authProvider, providerConfig, ServletAppUtils.getOriginFromRequestOrThrow(request));
}
@Override
@@ -22,6 +22,7 @@ import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
import io.cloudbeaver.service.auth.model.user.WebAuthProviderInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -44,6 +45,7 @@ public interface DBWServiceAuth extends DBWService {
@WebAction(authRequired = false)
WebAsyncAuthStatus federatedLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
@@ -62,6 +64,7 @@ public interface DBWServiceAuth extends DBWService {
@WebAction(authRequired = false)
WebLogoutInfo authLogout(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@Nullable String providerId,
@Nullable String configurationId
@@ -71,7 +74,7 @@ public interface DBWServiceAuth extends DBWService {
WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException;
@WebAction(authRequired = false)
WebAuthProviderInfo[] getAuthProviders();
WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException;
@WebAction()
boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException;
@@ -17,6 +17,7 @@
package io.cloudbeaver.service.auth;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.auth.impl.WebServiceAuthImpl;
@@ -49,12 +50,15 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
env.getArgument("taskId")
))
.dataFetcher("authLogoutExtended", env -> getService(env).authLogout(
GraphQLEndpoint.getServletRequest(env),
getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("configuration")
))
.dataFetcher("authLogout", env -> {
getService(env).authLogout(getWebSession(env, false),
getService(env).authLogout(
GraphQLEndpoint.getServletRequest(env),
getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("configuration"));
return true;
@@ -65,7 +69,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
CommonUtils.toBoolean(env.getArgument("linkUser"))
))
.dataFetcher("activeUser", env -> getService(env).activeUser(getWebSession(env, false)))
.dataFetcher("authProviders", env -> getService(env).getAuthProviders())
.dataFetcher("authProviders", env -> getService(env).getAuthProviders(GraphQLEndpoint.getServletRequest(env)))
.dataFetcher("authChangeLocalPassword", env -> getService(env).changeLocalPassword(
getWebSession(env),
env.getArgument("oldPassword"),
@@ -83,6 +87,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
env -> getService(env).setUserConfigurationParameters(getWebSession(env),
env.getArgument("preferences")))
.dataFetcher("federatedLogin", env -> getService(env).federatedLogin(
GraphQLEndpoint.getServletRequest(env),
getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("configuration"),
@@ -34,6 +34,8 @@ import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.auth.*;
import io.cloudbeaver.service.auth.model.user.WebAuthProviderInfo;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -42,6 +44,7 @@ import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
import org.jkiss.dbeaver.model.auth.SMSessionExternal;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMSubjectType;
import org.jkiss.dbeaver.model.security.exception.SMTooManySessionsException;
@@ -92,6 +95,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebAsyncAuthStatus federatedLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
@@ -106,7 +110,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
throw new DBWebException("Provider '" + providerId + "' is not federated");
}
try {
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, Map.of(), forceSessionsLogout);
Map<String, Object> authParameters = new HashMap<>();
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequestOrThrow(httpRequest));
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
throw new DBWebException("Unexpected auth status: " + smAuthInfo.getAuthStatus());
}
@@ -161,9 +168,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
if (authProviderDescriptor.isTrusted()) {
throw new DBWebException(authProviderDescriptor.getLabel() + " not allowed for authorization via GQL API");
}
if (authParameters == null) {
authParameters = Map.of();
}
SMController securityController = webSession.getSecurityController();
String currentSmSessionId = (webSession.getUser() == null || CBApplication.getInstance().isConfigurationMode())
? null
@@ -210,6 +215,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebLogoutInfo authLogout(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@Nullable String providerId,
@Nullable String configurationId
@@ -221,6 +227,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
List<WebAuthInfo> removedInfos = webSession.removeAuthInfo(providerId);
List<String> logoutUrls = new ArrayList<>();
var cbApp = CBApplication.getInstance();
String origin = ServletAppUtils.getOriginFromRequestOrThrow(httpRequest);
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
.getInstance() instanceof SMSignOutLinkProvider provider
@@ -233,12 +240,15 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
continue;
}
String logoutUrl;
if (removedInfo.getAuthSession() instanceof SMSessionExternal externalSession) {
logoutUrl = provider.getUserSignOutLink(providerConfig,
externalSession.getAuthParameters());
externalSession.getAuthParameters(), origin
);
} else {
logoutUrl = provider.getUserSignOutLink(providerConfig,
Map.of());
Map.of(), origin
);
}
if (CommonUtils.isNotEmpty(logoutUrl)) {
logoutUrls.add(logoutUrl);
@@ -286,9 +296,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
@Override
public WebAuthProviderInfo[] getAuthProviders() {
public WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream().map(WebAuthProviderInfo::new)
.stream()
.map(descriptor -> new WebAuthProviderInfo(descriptor, origin))
.toArray(WebAuthProviderInfo[]::new);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,9 +42,11 @@ public class WebAuthProviderInfo {
@NotNull
private final WebAuthProviderDescriptor descriptor;
private final String origin;
public WebAuthProviderInfo(@NotNull WebAuthProviderDescriptor descriptor) {
public WebAuthProviderInfo(@NotNull WebAuthProviderDescriptor descriptor, String origin) {
this.descriptor = descriptor;
this.origin = origin;
}
public String getId() {
@@ -109,7 +111,7 @@ public class WebAuthProviderInfo {
List<WebAuthProviderConfiguration> result = new ArrayList<>();
for (SMAuthProviderCustomConfiguration cfg : CBApplication.getInstance().getAppConfiguration().getAuthCustomConfigurations()) {
if (!cfg.isDisabled() && getId().equals(cfg.getProvider())) {
result.add(new WebAuthProviderConfiguration(descriptor, cfg));
result.add(new WebAuthProviderConfiguration(descriptor, cfg, origin));
}
}
return result;
@@ -125,7 +127,7 @@ public class WebAuthProviderInfo {
}
public WebAuthProviderConfiguration getTemplateConfiguration() {
return new WebAuthProviderConfiguration(descriptor, TEMPLATE_CONFIG);
return new WebAuthProviderConfiguration(descriptor, TEMPLATE_CONFIG, "{origin}");
}
@Override
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,12 +133,20 @@ public class RPAuthProvider implements SMAuthProviderExternal<SMSession>, SMSign
@NotNull
@Override
public String getCommonSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException {
public String getCommonSignOutLink(
@NotNull String providerId,
@NotNull Map<String, Object> providerConfig,
@NotNull String origin
) throws DBException {
return providerConfig.get(LOGOUT_URL) != null ? providerConfig.get(LOGOUT_URL).toString() : "";
}
@Override
public String getUserSignOutLink(@NotNull SMAuthProviderCustomConfiguration providerConfig, @NotNull Map<String, Object> userCredentials) throws DBException {
public String getUserSignOutLink(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials,
@NotNull String origin
) throws DBException {
return providerConfig.getParameters().get(LOGOUT_URL) != null ?
providerConfig.getParameters().get(LOGOUT_URL).toString() :
null;
@@ -39,6 +39,7 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPConnectionInformation;
import org.jkiss.dbeaver.model.DBPPage;
import org.jkiss.dbeaver.model.auth.*;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction;
@@ -61,6 +62,7 @@ import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.SecurityUtils;
import java.lang.reflect.Type;
import java.net.URI;
import java.sql.*;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -1633,7 +1635,14 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
throw e;
}
}
boolean isFederatedAuth = SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass());
if (isFederatedAuth) {
String userOrigin = JSONUtils.getString(userCredentials, SMConstants.USER_ORIGIN);
if (CommonUtils.isEmpty(userOrigin)) {
throw new SMException("User origin not found in authentication data");
}
filteredUserCreds.put(SMConstants.USER_ORIGIN, modifyOrigin(userOrigin));
}
authAttemptId = createNewAuthAttempt(
SMAuthStatus.IN_PROGRESS,
authProviderId,
@@ -1647,15 +1656,21 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
forceSessionsLogout
);
if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) {
if (isFederatedAuth) {
String userOrigin = JSONUtils.getString(filteredUserCreds, SMConstants.USER_ORIGIN);
//async auth
var authProviderFederated = (SMAuthProviderFederated) authProviderInstance;
String signInLink = buildRedirectLink(authProviderFederated.getSignInLink(authProviderConfigurationId),
authAttemptId);
String signInLink = buildRedirectLink(
authProviderFederated.getSignInLink(authProviderConfigurationId, userOrigin),
authAttemptId
);
String signOutLink = authProviderFederated.getCommonSignOutLink(authProviderConfigurationId,
providerConfig.getParameters());
Map<SMAuthConfigurationReference, Object> authData = Map.of(new SMAuthConfigurationReference(authProviderId,
authProviderConfigurationId), filteredUserCreds);
providerConfig.getParameters(), userOrigin
);
Map<SMAuthConfigurationReference, Object> authData = Map.of(
new SMAuthConfigurationReference(authProviderId, authProviderConfigurationId),
filteredUserCreds
);
return SMAuthInfo.inProgress(
authAttemptId,
signInLink,
@@ -1689,6 +1704,23 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
}
}
@NotNull
protected String modifyOrigin(@NotNull String origin) {
StringBuilder finalOrigin = new StringBuilder();
URI uri = URI.create(origin);
finalOrigin.append(uri.getScheme())
.append("://")
.append(uri.getHost());
if (uri.getPort() > 0 && application.getServerPort() != uri.getPort()) {
finalOrigin.append(":").append(application.getServerPort());
} else {
return origin;
}
finalOrigin.append(uri.getPath());
return finalOrigin.toString();
}
private Map<String, Object> filterSecuredUserData(
Map<String, Object> userIdentifyingCredentials,
WebAuthProviderDescriptor authProviderDescriptor
@@ -1958,13 +1990,18 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
WebAuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId);
var authProviderInstance = authProviderDescriptor.getInstance();
if (authProviderInstance instanceof SMAuthProviderFederated providerFederated) {
signInLink = buildRedirectLink(providerFederated.getRedirectLink(
authProviderConfiguration,
Map.of()), authId);
signOutLink = providerFederated.getUserSignOutLink(
application.getAuthConfiguration()
.getAuthProviderConfiguration(authProviderConfiguration),
authProviderData);
String userOrigin = JSONUtils.getString(authProviderData, SMConstants.USER_ORIGIN);
if(CommonUtils.isNotEmpty(userOrigin)){
signInLink = buildRedirectLink(
providerFederated.getRedirectLink(authProviderConfiguration, Map.of(), userOrigin),
authId
);
signOutLink = providerFederated.getUserSignOutLink(
application.getAuthConfiguration()
.getAuthProviderConfiguration(authProviderConfiguration),
authProviderData, userOrigin
);
}
}
}
@@ -3288,7 +3325,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
return authProvider;
}
private String buildRedirectLink(String originalLink, String authId) {
private String buildRedirectLink(@NotNull String originalLink, @NotNull String authId) {
return originalLink + "?authId=" + authId;
}