mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
2
Commits
release_26_0_2
...
25.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bee81952d | ||
|
|
698dc8a312 |
@@ -28,7 +28,7 @@
|
||||
},
|
||||
|
||||
expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
|
||||
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:true}",
|
||||
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:disable}",
|
||||
develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
|
||||
|
||||
enableSecurityManager: false,
|
||||
|
||||
@@ -33,6 +33,7 @@ public class CBConstants {
|
||||
public static final String PARAM_SERVER_HOST = "serverHost";
|
||||
public static final String PARAM_SERVER_NAME = "serverName";
|
||||
public static final String PARAM_FORCE_HTTPS = "forceHttps";
|
||||
public static final String PARAM_BIND_SESSION_TO_IP = "bindSessionToIp";
|
||||
public static final String PARAM_SUPPORTED_HOSTS = "supportedHosts";
|
||||
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
|
||||
public static final String PARAM_CONTENT_ROOT = "contentRoot";
|
||||
@@ -94,4 +95,7 @@ public class CBConstants {
|
||||
public static final String ADMIN_AUTO_GRANT = "auto-grant";
|
||||
public static final String HOST_LOCALHOST = "localhost";
|
||||
public static final String HOST_127_0_0_1 = "127.0.0.1";
|
||||
|
||||
public static final String BIND_SESSION_ENABLE = "enable";
|
||||
public static final String BIND_SESSION_DISABLE = "disable";
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@@ -47,6 +48,11 @@ public class ServletAppUtils {
|
||||
|
||||
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
|
||||
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
|
||||
private static final Set<Integer> DEFAULT_PORTS = Set.of(
|
||||
80,
|
||||
443
|
||||
);
|
||||
|
||||
|
||||
private static final Log log = Log.getLog(ServletAppUtils.class);
|
||||
|
||||
@@ -309,7 +315,26 @@ public class ServletAppUtils {
|
||||
if (!origin.endsWith(rootUri)) {
|
||||
origin = origin + "/" + rootUri + "/";
|
||||
}
|
||||
return removeSideSlashes(origin);
|
||||
|
||||
origin = removeSideSlashes(origin);
|
||||
URI uri = URI.create(origin);
|
||||
if (DEFAULT_PORTS.contains(uri.getPort())) {
|
||||
try {
|
||||
origin = new URI(
|
||||
uri.getScheme(),
|
||||
uri.getUserInfo(),
|
||||
uri.getHost(),
|
||||
-1,
|
||||
uri.getPath(),
|
||||
uri.getQuery(),
|
||||
uri.getFragment()
|
||||
).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
log.error("Failed to create URI without port", e);
|
||||
}
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
public static String getRootUrlFromUri(@NotNull URI uri) {
|
||||
|
||||
@@ -16,5 +16,7 @@ extend type ServerConfig {
|
||||
forceHttps: Boolean! @since(version: "25.1.3")
|
||||
"List of allowed hosts to use the application - if empty, all hosts are allowed"
|
||||
supportedHosts: [String!]! @since(version: "25.1.3")
|
||||
"Indicates whether the session should be linked to the user's IP address"
|
||||
bindSessionToIp: String! @since(version: "25.1.4")
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -99,4 +99,10 @@ public class CBWebServerConfig extends WebServerConfig {
|
||||
public List<String> getSupportedHosts() {
|
||||
return cbApp.getServerConfiguration().getSupportedHosts();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getBindSessionToIp() {
|
||||
return cbApp.getServerConfiguration().getBindSessionToIp();
|
||||
}
|
||||
}
|
||||
|
||||
+18
-4
@@ -55,7 +55,8 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
@SerializedName("database")
|
||||
private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig();
|
||||
private String staticContent = "";
|
||||
private boolean bindSessionToIp = true;
|
||||
@NotNull
|
||||
private String bindSessionToIp = CBConstants.BIND_SESSION_DISABLE;
|
||||
|
||||
public CBServerConfig() {
|
||||
this.securityManagerConfiguration = createSecurityManagerConfiguration();
|
||||
@@ -204,9 +205,17 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
LinkedHashSet<String> uniqueHosts = new LinkedHashSet<>();
|
||||
for (String host : availableHosts) {
|
||||
try {
|
||||
if (!host.startsWith("http://") && !host.startsWith("https://")) {
|
||||
host = "http://" + host; // Default to HTTP if no scheme is provided to avoid uri parse exception
|
||||
}
|
||||
URI uri = URI.create(host);
|
||||
String hostName = ServletAppUtils.removeSideSlashes(uri.getHost() != null ? uri.getHost() : host);
|
||||
uniqueHosts.add(hostName);
|
||||
StringBuilder hostNameBuilder = new StringBuilder();
|
||||
hostNameBuilder.append(ServletAppUtils.removeSideSlashes(uri.getHost() != null ? uri.getHost() : host));
|
||||
if (uri.getPort() > 0) {
|
||||
hostNameBuilder.append(':')
|
||||
.append(uri.getPort());
|
||||
}
|
||||
uniqueHosts.add(hostNameBuilder.toString());
|
||||
} catch (Exception e) {
|
||||
log.error("Invalid host URI: " + host, e);
|
||||
}
|
||||
@@ -215,7 +224,12 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
this.supportedHosts.addAll(uniqueHosts);
|
||||
}
|
||||
|
||||
public boolean isBindSessionToIp() {
|
||||
@NotNull
|
||||
public String getBindSessionToIp() {
|
||||
return bindSessionToIp;
|
||||
}
|
||||
|
||||
public void setBindSessionToIp(@NotNull String bindSessionToIp) {
|
||||
this.bindSessionToIp = bindSessionToIp;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-12
@@ -91,18 +91,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
Path runtimeConfigPath = getRuntimeAppConfigPath();
|
||||
if (Files.exists(runtimeConfigPath)) {
|
||||
log.debug("Runtime configuration [" + runtimeConfigPath.toAbsolutePath() + "]");
|
||||
Map<String, Object> runtimeConfigMap = loadConfiguration(runtimeConfigPath);
|
||||
Map<String, Object> serverConfigMap = JSONUtils.getObject(runtimeConfigMap, CBConstants.PARAM_SERVER_CONFIGURATION);
|
||||
if (!serverConfigMap.containsKey(CBConstants.PARAM_FORCE_HTTPS)) {
|
||||
CBServerConfig serverConfig = getServerConfiguration();
|
||||
// enable https for legacy configurations
|
||||
if (CommonUtils.isEmpty(serverConfig.getSupportedHosts())
|
||||
&& CommonUtils.isNotEmpty(serverConfig.getServerURL())
|
||||
&& serverConfig.getServerURL().startsWith("https://")
|
||||
) {
|
||||
serverConfig.setForceHttps(true);
|
||||
}
|
||||
}
|
||||
loadConfiguration(runtimeConfigPath);
|
||||
}
|
||||
|
||||
// Set default preferences
|
||||
@@ -554,6 +543,12 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
CBConstants.PARAM_SUPPORTED_HOSTS,
|
||||
serverConfig.getSupportedHosts()
|
||||
);
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_BIND_SESSION_TO_IP,
|
||||
serverConfig.getBindSessionToIp()
|
||||
);
|
||||
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
|
||||
CBConstants.PARAM_PRODUCT_SETTINGS);
|
||||
|
||||
+5
-1
@@ -111,7 +111,11 @@ public class RequestHostFilter implements Filter {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String requestHost = originUri.getHost();
|
||||
var requestHostBuilder = new StringBuilder(originUri.getHost());
|
||||
if (originUri.getPort() > -1) {
|
||||
requestHostBuilder.append(':').append(originUri.getPort());
|
||||
}
|
||||
String requestHost = requestHostBuilder.toString();
|
||||
if (!availableHosts.contains(requestHost)) {
|
||||
log.warn("Request host '" + requestHost + "' is not allowed. Redirect to default: " + availableHosts);
|
||||
redirectToDefaultHost((HttpServletResponse) response, httpRequest, availableHosts);
|
||||
|
||||
+13
-2
@@ -22,6 +22,7 @@ import io.cloudbeaver.model.session.*;
|
||||
import io.cloudbeaver.registry.WebHandlerRegistry;
|
||||
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.WebAppSessionManager;
|
||||
import io.cloudbeaver.server.events.WSWebUtils;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
@@ -156,8 +157,15 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
validateSessionIp(request, webSession);
|
||||
|
||||
return webSession;
|
||||
}
|
||||
|
||||
private void validateSessionIp(@NotNull HttpServletRequest request, WebSession webSession) {
|
||||
boolean bindingEnabled = isSessionBindingEnabled(request);
|
||||
String currentRemote = request.getRemoteAddr();
|
||||
if (application.getServerConfiguration().isBindSessionToIp()
|
||||
if (bindingEnabled
|
||||
&& (CommonUtils.isEmpty(currentRemote) || !currentRemote.equals(webSession.getLastRemoteAddr()))
|
||||
) {
|
||||
var error = new DBWebException(
|
||||
@@ -169,8 +177,11 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
webSession.addSessionError(error);
|
||||
closeSession(webSession.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
return webSession;
|
||||
protected boolean isSessionBindingEnabled(@NotNull HttpServletRequest request) {
|
||||
String bindingState = application.getServerConfiguration().getBindSessionToIp();
|
||||
return CBConstants.BIND_SESSION_ENABLE.equalsIgnoreCase(bindingState) || Boolean.parseBoolean(bindingState);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -127,7 +127,8 @@ input ServerConfigInput {
|
||||
|
||||
sessionExpireTime: Int
|
||||
forceHttps: Boolean @since(version: "25.1.3")
|
||||
supportedHosts: [String!]! @since(version: "25.1.3")
|
||||
supportedHosts: [String!] @since(version: "25.1.3")
|
||||
bindSessionToIp: String @since(version: "25.1.4")
|
||||
}
|
||||
|
||||
input AdminUserFilterInput {
|
||||
|
||||
+13
@@ -55,6 +55,8 @@ public class AdminServerConfig {
|
||||
private final List<String> supportedHosts;
|
||||
|
||||
private long sessionExpireTime;
|
||||
@Nullable
|
||||
private String bindSessionToIp;
|
||||
|
||||
public AdminServerConfig(Map<String, Object> params) {
|
||||
this.serverName = JSONUtils.getString(params, "serverName");
|
||||
@@ -115,6 +117,12 @@ public class AdminServerConfig {
|
||||
} else {
|
||||
this.supportedHosts = null;
|
||||
}
|
||||
|
||||
if (params.containsKey("bindSessionToIp")) {
|
||||
this.bindSessionToIp = JSONUtils.getString(params, "bindSessionToIp");
|
||||
} else {
|
||||
this.bindSessionToIp = CBConstants.BIND_SESSION_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
@@ -206,4 +214,9 @@ public class AdminServerConfig {
|
||||
public Boolean getForceHttps() {
|
||||
return forceHttps;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBindSessionToIp() {
|
||||
return bindSessionToIp;
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -611,6 +611,9 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
if (config.getSupportedHosts() != null) {
|
||||
serverConfig.setSupportedHosts(config.getSupportedHosts());
|
||||
}
|
||||
if (config.getBindSessionToIp() != null) {
|
||||
serverConfig.setBindSessionToIp(config.getBindSessionToIp());
|
||||
}
|
||||
}
|
||||
|
||||
if (CommonUtils.isEmpty(adminName)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ export const defaultServerConfig: (productConfiguration?: Record<string, any>) =
|
||||
supportsWorkspaces: false,
|
||||
sessionExpireTime: 6000000,
|
||||
anonymousAccessEnabled: true,
|
||||
bindSessionToIp: 'disable',
|
||||
adminCredentialsSaveEnabled: true,
|
||||
publicCredentialsSaveEnabled: true,
|
||||
secretManagerEnabled: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ fragment ServerConfig on ServerConfig {
|
||||
sessionExpireTime
|
||||
|
||||
anonymousAccessEnabled
|
||||
bindSessionToIp
|
||||
|
||||
adminCredentialsSaveEnabled
|
||||
publicCredentialsSaveEnabled
|
||||
|
||||
+37
-34
@@ -7,39 +7,42 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Group, GroupTitle, Switch, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { IServerConfig } from '../IServerConfigurationFormPartState.js';
|
||||
import { Group, GroupTitle, Placeholder, Switch, useTranslate, type PlaceholderComponent } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ServerConfigurationService, type IConfigurationPlaceholderProps } from '../ServerConfigurationService.js';
|
||||
|
||||
interface Props {
|
||||
serverConfig: IServerConfig;
|
||||
}
|
||||
export const ServerConfigurationSecurityForm: PlaceholderComponent<IConfigurationPlaceholderProps> = observer(
|
||||
function ServerConfigurationSecurityForm({ state, configurationWizard }) {
|
||||
const translate = useTranslate();
|
||||
const serverConfig = state.serverConfig;
|
||||
const serverConfigurationService = useService(ServerConfigurationService);
|
||||
|
||||
export const ServerConfigurationSecurityForm = observer<Props>(function ServerConfigurationSecurityForm({ serverConfig }) {
|
||||
const translate = useTranslate();
|
||||
return (
|
||||
<Group form gap>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_security')}</GroupTitle>
|
||||
<Switch
|
||||
name="adminCredentialsSaveEnabled"
|
||||
state={serverConfig}
|
||||
description={translate('administration_configuration_wizard_configuration_security_admin_credentials_description')}
|
||||
mod={['primary']}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_security_admin_credentials')}
|
||||
</Switch>
|
||||
<Switch
|
||||
name="publicCredentialsSaveEnabled"
|
||||
state={serverConfig}
|
||||
description={translate('administration_configuration_wizard_configuration_security_public_credentials_description')}
|
||||
mod={['primary']}
|
||||
disabled={!serverConfig.adminCredentialsSaveEnabled}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_security_public_credentials')}
|
||||
</Switch>
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Group form gap>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_security')}</GroupTitle>
|
||||
<Switch
|
||||
name="adminCredentialsSaveEnabled"
|
||||
state={serverConfig}
|
||||
description={translate('administration_configuration_wizard_configuration_security_admin_credentials_description')}
|
||||
mod={['primary']}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_security_admin_credentials')}
|
||||
</Switch>
|
||||
<Switch
|
||||
name="publicCredentialsSaveEnabled"
|
||||
state={serverConfig}
|
||||
description={translate('administration_configuration_wizard_configuration_security_public_credentials_description')}
|
||||
mod={['primary']}
|
||||
disabled={!serverConfig.adminCredentialsSaveEnabled}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_security_public_credentials')}
|
||||
</Switch>
|
||||
<Placeholder container={serverConfigurationService.securitySettingsContainer} configurationWizard={configurationWizard} state={state} />
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ const ServerConfigurationFormPartStateConfigSchema = schema.object({
|
||||
sessionExpireTime: schema.number().optional(),
|
||||
forceHttps: schema.boolean().optional(),
|
||||
supportedHosts: schema.string(),
|
||||
bindSessionToIp: schema.string().optional(),
|
||||
});
|
||||
|
||||
const ServerConfigurationFormPartStateNavigatorSchema = schema.object({
|
||||
|
||||
+1
@@ -167,6 +167,7 @@ export class ServerConfigurationFormPart extends FormPart<IServerConfigurationFo
|
||||
secretManagerEnabled: config?.secretManagerEnabled ?? false,
|
||||
supportedHosts: config?.supportedHosts.join(SUPPORTED_HOSTS_SPLITTER) ?? '',
|
||||
forceHttps: config?.forceHttps ?? true,
|
||||
bindSessionToIp: config?.bindSessionToIp,
|
||||
},
|
||||
navigatorConfig: { ...this.state.navigatorConfig, ...defaultNavigatorSettings },
|
||||
});
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
|
||||
<Placeholder container={serverConfigurationService.pluginsContainer} configurationWizard={configurationWizard} state={part.state} />
|
||||
</Group>
|
||||
<Placeholder container={serverConfigurationService.configurationContainer} configurationWizard={configurationWizard} state={part.state} />
|
||||
<ServerConfigurationSecurityForm serverConfig={part.state.serverConfig} />
|
||||
<ServerConfigurationSecurityForm configurationWizard={configurationWizard} state={part.state} />
|
||||
<ServerConfigurationDriversForm initialServerConfig={part.initialState.serverConfig} serverConfig={part.state.serverConfig} />
|
||||
</Container>
|
||||
</Form>
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,11 +25,13 @@ export class ServerConfigurationService {
|
||||
|
||||
readonly configurationContainer: PlaceholderContainer<IConfigurationPlaceholderProps>;
|
||||
readonly pluginsContainer: PlaceholderContainer<IConfigurationPlaceholderProps>;
|
||||
readonly securitySettingsContainer: PlaceholderContainer<IConfigurationPlaceholderProps>;
|
||||
|
||||
constructor(private readonly serverConfigurationFormService: ServerConfigurationFormService) {
|
||||
this.isDone = false;
|
||||
this.configurationContainer = new PlaceholderContainer();
|
||||
this.pluginsContainer = new PlaceholderContainer();
|
||||
this.securitySettingsContainer = new PlaceholderContainer();
|
||||
|
||||
this.serverConfigurationFormService.onValidate.addHandler((data, contexts) => {
|
||||
const validation = contexts.getContext(formValidationContext);
|
||||
|
||||
@@ -68,7 +68,6 @@ export default [
|
||||
],
|
||||
['administration_configuration_wizard_configuration_security_public_credentials', 'Save users credentials'],
|
||||
['administration_configuration_wizard_configuration_security_public_credentials_description', 'Allows to save credentials for non-admin users'],
|
||||
|
||||
['administration_configuration_wizard_configuration_navigator', 'Navigator'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_folders', 'Hide Folders'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_schemas', 'Hide Schemas'],
|
||||
|
||||
@@ -74,7 +74,6 @@ export default [
|
||||
'administration_configuration_wizard_configuration_security_public_credentials_description',
|
||||
"Permet d'enregistrer les identifiants pour les utilisateurs non administrateurs",
|
||||
],
|
||||
|
||||
['administration_configuration_wizard_configuration_navigator', 'Navigateur'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_folders', 'Masquer les dossiers'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_schemas', 'Masquer les schémas'],
|
||||
|
||||
@@ -73,7 +73,6 @@ export default [
|
||||
'administration_configuration_wizard_configuration_security_public_credentials_description',
|
||||
'Permetti di salvare le credenziali per gli utenti non amministratori',
|
||||
],
|
||||
|
||||
['administration_configuration_wizard_configuration_navigator', 'Navigatore'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_folders', 'Nascondi le Cartelle'],
|
||||
['administration_configuration_wizard_configuration_navigator_hide_schemas', 'Nascondi gli Schemi'],
|
||||
|
||||
Reference in New Issue
Block a user