Compare commits

...
5 Commits
Author SHA1 Message Date
Ruslan MusaevandDaria Marutkina 5e30bbdc77 dbeaver/pro#7089 fix migration hotfix (#3813)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-08 16:04:09 +02:00
Alexey Potsetsuev 7419bd1412 dbeaver/pro#7111 fix: doubled hot keys handler (#3808) 2025-10-08 16:07:27 +08:00
Alexander Skoblikov 68758381aa dbeaver/pro#6880 more correct origin parsing (#3744) 2025-09-15 08:36:31 +00:00
Alexander Skoblikov 23ae5b410e Hotfix/6703 (#3703)
* dbeaver/pro#6703 use local host address by default

* dbeaver/pro#6703 rename method
2025-08-27 12:20:24 +02:00
Alexander SkoblikovandWroud a7d4ec7e6f Hotfix/6462 core multiurls redirect doesnt work on url change (#3700)
* dbeaver/pro#6462 error instead of redirect for gql requests

* feat: force redirect on supportedHosts change

* fix: replace redirect with warning message

* fix: prevent active host removal

* fix: error message and ignore when on IP address

---------

Co-authored-by: Wroud <wrouds@gmail.com>
2025-08-26 15:01:32 +02:00
25 changed files with 249 additions and 61 deletions
@@ -292,7 +292,7 @@ public class ServletAppUtils {
}
@NotNull
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
String origin = request.getHeader(HEADER_ORIGIN);
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
@@ -353,7 +353,22 @@ public class ServletAppUtils {
if (CommonUtils.isEmpty(rootUri)) {
return uri;
}
String[] split = uri.split(rootUri);
return split[0];
rootUri = "/" + rootUri;
if (!uri.endsWith(rootUri)) {
rootUri = rootUri + "/";
}
return substringBeforeLast(uri, rootUri);
}
public static String substringBeforeLast(
@NotNull String string,
@NotNull String separator
) {
if (CommonUtils.isNotEmpty(string) && CommonUtils.isNotEmpty(separator)) {
int pos = string.lastIndexOf(separator);
return pos == -1 ? string : string.substring(0, pos);
} else {
return string;
}
}
}
@@ -103,7 +103,6 @@ public abstract class CBApplication<T extends CBServerConfig>
// Persistence
protected SMAdminController securityController;
private boolean configurationMode = false;
private String localHostAddress;
protected String containerId;
private final List<InetAddress> localInetAddresses = new ArrayList<>();
@@ -226,15 +225,6 @@ public abstract class CBApplication<T extends CBServerConfig>
eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
Location instanceLoc = Platform.getInstanceLocation();
try {
@@ -529,7 +519,7 @@ public abstract class CBApplication<T extends CBServerConfig>
}
public String getLocalHostAddress() {
return localHostAddress;
return getServerConfigurationController().getLocalHostAddress();
}
public List<InetAddress> getLocalInetAddresses() {
@@ -44,8 +44,6 @@ import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -66,6 +64,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
protected final Path homeDirectory;
private final Map<String, String> externalProperties = new LinkedHashMap<>();
private final Map<String, Object> originalConfigurationProperties = new LinkedHashMap<>();
private String localHostAddress;
protected CBServerConfigurationController(@NotNull T serverConfiguration, @NotNull Path homeDirectory) {
super(homeDirectory);
@@ -80,6 +79,15 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
@Override
public void loadServerConfiguration(Path configPath) throws DBException {
log.debug("Using configuration [" + configPath + "]");
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
if (!Files.exists(configPath)) {
log.error("Configuration file " + configPath + " doesn't exist. Use defaults.");
@@ -162,12 +170,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = CBConstants.HOST_LOCALHOST;
}
hostName = getLocalHostAddress();
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
}
@@ -669,4 +672,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
public void validateFinalServerConfiguration() throws DBException {
}
public String getLocalHostAddress() {
return localHostAddress;
}
}
@@ -120,12 +120,13 @@ public class CBJettyServer {
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
servletContextHandler.addServlet(
new ServletHolder(
"graphql",
endpoint
),
serverConfiguration.getServicesURI() + "gql/*"
gqlServletPath
);
servletContextHandler.addEventListener(new CBServerContextListener(application));
@@ -157,7 +158,11 @@ public class CBJettyServer {
}
}
}
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(application, excludedFilterPaths));
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(
application,
excludedFilterPaths,
Set.of(gqlServletPath)
));
servletContextHandler.addFilter(hostsFilter, "/*", null);
@@ -39,23 +39,35 @@ public class RequestHostFilter implements Filter {
@NotNull
private final CBApplication<?> application;
private final Set<String> excludedPaths = new HashSet<>();
private final Set<String> errorPaths = new HashSet<>();
public RequestHostFilter(@NotNull CBApplication<?> application, @NotNull Set<String> excludedPaths) {
public RequestHostFilter(
@NotNull CBApplication<?> application,
@NotNull Set<String> excludedPaths,
@NotNull Set<String> errorPaths
) {
this.application = application;
this.excludedPaths.addAll(
excludedPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
this.errorPaths.addAll(
errorPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean requestAllowed = true;
if (request instanceof HttpServletRequest httpRequest) {
CBServerConfig serverConfig = application.getServerConfiguration();
URI originUri;
try {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(httpRequest);
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
originUri = URI.create(origin);
} catch (Exception e) {
log.error("Failed to get origin from request", e);
@@ -64,7 +76,8 @@ public class RequestHostFilter implements Filter {
}
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
String servletPath = httpRequest.getServletPath();
if (!isIpAddress) {
requestAllowed = isIpAddress;
if (!requestAllowed) {
if (CommonUtils.isNotEmpty(servletPath)) {
for (String excludedPath : excludedPaths) {
if (servletPath.contains(excludedPath)) {
@@ -73,17 +86,21 @@ public class RequestHostFilter implements Filter {
}
}
}
validateHosts(serverConfig, httpRequest, response, originUri);
requestAllowed = validateHosts(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
if (requestAllowed) {
requestAllowed = validateSchema(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
validateSchema(serverConfig, httpRequest, response, originUri);
}
chain.doFilter(request, response);
if (requestAllowed) {
chain.doFilter(request, response);
}
}
private void validateSchema(
private boolean validateSchema(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull ServletResponse response,
@NotNull HttpServletResponse response,
@NotNull URI originUri
) {
boolean httpsExpected = serverConfig.isForceHttps();
@@ -100,22 +117,24 @@ public class RequestHostFilter implements Filter {
redirectUrlBuilder.append("?")
.append(httpRequest.getQueryString());
}
((HttpServletResponse) response).sendRedirect(redirectUrlBuilder.toString());
response.sendRedirect(redirectUrlBuilder.toString());
return false;
}
} catch (Exception e) {
log.error("Failed to redirect to HTTPS", e);
}
return true;
}
private void validateHosts(
private boolean validateHosts(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull ServletResponse response,
@NotNull HttpServletResponse response,
URI originUri
) throws IOException {
List<String> availableHosts = serverConfig.getSupportedHosts();
if (CommonUtils.isEmpty(availableHosts)) {
return;
return true;
}
try {
var requestHostBuilder = new StringBuilder(originUri.getHost());
@@ -124,13 +143,25 @@ public class RequestHostFilter implements Filter {
}
String requestHost = requestHostBuilder.toString();
if (!availableHosts.contains(requestHost)) {
for (String errorPath : errorPaths) {
if (httpRequest.getServletPath().contains(errorPath)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(
"Request host '" + requestHost + "' is not allowed. Available hosts: " + availableHosts
);
return false;
}
}
log.warn("Request host '" + requestHost + "' is not allowed. Redirect to default: " + availableHosts);
redirectToDefaultHost((HttpServletResponse) response, httpRequest, availableHosts);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
} catch (Throwable e) {
log.error(e.getMessage(), e);
redirectToDefaultHost((HttpServletResponse) response, httpRequest, availableHosts);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
return true;
}
private void redirectToDefaultHost(
@@ -494,7 +494,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@NotNull WebSession webSession,
@Nullable String providerId
) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
String origin = ServletAppUtils.getOriginFromRequest(request);
List<WebAuthProviderConfiguration> result = new ArrayList<>();
for (SMAuthProviderCustomConfiguration cfg : CBApplication.getInstance().getAppConfiguration().getAuthCustomConfigurations()) {
if (providerId != null && !providerId.equals(cfg.getProvider())) {
@@ -544,7 +544,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
providerConfig.getProvider(),
webSession.getUserId()
));
return new WebAuthProviderConfiguration(authProvider, providerConfig, ServletAppUtils.getOriginFromRequestOrThrow(request));
return new WebAuthProviderConfiguration(authProvider, providerConfig, ServletAppUtils.getOriginFromRequest(request));
}
@Override
@@ -111,7 +111,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
try {
Map<String, Object> authParameters = new HashMap<>();
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequestOrThrow(httpRequest));
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequest(httpRequest));
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
@@ -227,7 +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);
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
.getInstance() instanceof SMSignOutLinkProvider provider
@@ -297,7 +297,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
String origin = ServletAppUtils.getOriginFromRequest(request);
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream()
.map(descriptor -> new WebAuthProviderInfo(descriptor, origin))
@@ -240,3 +240,46 @@ CREATE TABLE {table_prefix}CB_AUTH_ATTEMPT_INFO
PRIMARY KEY (AUTH_ID, AUTH_PROVIDER_ID),
FOREIGN KEY (AUTH_ID) REFERENCES {table_prefix}CB_AUTH_ATTEMPT (AUTH_ID) ON DELETE CASCADE
);
-- Secrets
CREATE TABLE {table_prefix}CB_ACCESS_TOKEN
(
TOKEN_ID VARCHAR(128) NOT NULL,
USER_ID VARCHAR(128) NOT NULL,
TOKEN_NAME VARCHAR(128) NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
EXPIRATION_TIME TIMESTAMP NULL,
PRIMARY KEY (USER_ID, TOKEN_ID),
UNIQUE (USER_ID, TOKEN_NAME),
FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER(USER_ID) ON DELETE CASCADE
);
-- Tasks
CREATE TABLE {table_prefix}CB_TASKS
(
TASK_ID VARCHAR(128) NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
TIMEOUT INTEGER NOT NULL,
PRIMARY KEY (TASK_ID)
);
CREATE TABLE {table_prefix}CB_SUBJECT_SECRETS
(
SUBJECT_ID VARCHAR(128) NOT NULL,
SECRET_ID VARCHAR(255) NOT NULL,
PROJECT_ID VARCHAR(128),
OBJECT_TYPE VARCHAR(32),
OBJECT_ID VARCHAR(128),
SECRET_VALUE TEXT NOT NULL,
ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (SUBJECT_ID, SECRET_ID),
FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE
);
@@ -23,10 +23,12 @@ import { FieldDescription } from './FieldDescription.js';
import { FieldLabel } from './FieldLabel.js';
import { FormContext } from './FormContext.js';
import textareaStyle from './Textarea.module.css';
import { useMergeRefs } from '../useMergeRefs.js';
type BaseProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange' | 'style'> &
ILayoutSizeProps & {
description?: string;
ref?: React.Ref<HTMLTextAreaElement | null>;
description?: React.ReactNode;
labelTooltip?: string;
embedded?: boolean;
cursorInitiallyAtEnd?: boolean;
@@ -53,6 +55,7 @@ interface TextareaType {
}
export const Textarea: TextareaType = observer(function Textarea({
ref,
name,
value: controlledValue,
state,
@@ -71,6 +74,7 @@ export const Textarea: TextareaType = observer(function Textarea({
const translate = useTranslate();
const inputId = useId();
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const mergedRef = useMergeRefs(...[textareaRef, ref!].filter(Boolean));
const layoutProps = getLayoutProps(rest);
rest = filterLayoutFakeProps(rest);
const styles = useS(textareaStyle);
@@ -108,7 +112,7 @@ export const Textarea: TextareaType = observer(function Textarea({
</FieldLabel>
<textarea
{...rest}
ref={textareaRef}
ref={mergedRef}
id={inputId}
required={required}
className={s(styles, { textarea: true })}
@@ -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.
@@ -13,15 +13,17 @@ import { useTranslate } from '../localization/useTranslate.js';
import { useExecutor } from '../useExecutor.js';
import { FormContext } from './FormContext.js';
export function useCustomInputValidation<T = void>(validation: (value: T) => string | null): React.RefObject<HTMLInputElement | null> {
export function useCustomInputValidation<T = void, TType extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement>(
validation: (value: T) => string | null,
): React.RefObject<TType | null> {
const context = useContext(FormContext);
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<TType | null>(null);
const translate = useTranslate();
function validate(element: HTMLInputElement): boolean {
function validate(element: TType): boolean {
let value: T = undefined as unknown as T;
if (element instanceof HTMLInputElement) {
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
value = element.value as unknown as T;
}
@@ -64,14 +66,14 @@ export function useCustomInputValidation<T = void>(validation: (value: T) => str
}
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
const target = event.target as TType;
if (target.validity.valid === false) {
validate(target);
}
}
function handleBlur(event: Event) {
const target = event.target as HTMLInputElement;
const target = event.target as TType;
if (target.validity.valid === true) {
validate(target);
}
+1
View File
@@ -31,6 +31,7 @@ export * from './getPathName.js';
export * from './getPathParent.js';
export * from './getPathParents.js';
export * from './getPathParts.js';
export * from './isIp.js';
export * from './GlobalConstants.js';
export * from './ILoadableState.js';
export * from './errorOf.js';
+17
View File
@@ -0,0 +1,17 @@
/*
* CloudBeaver - Cloud Database Manager
* 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.
*/
export function isIp(host: string): boolean {
// Matches IPv4 like 127.0.0.1
const ipv4 = /^(25[0-5]|2[0-4]\d|1\d\d|\d\d?)\.(25[0-5]|2[0-4]\d|1\d\d|\d\d?)\.(25[0-5]|2[0-4]\d|1\d\d|\d\d?)\.(25[0-5]|2[0-4]\d|1\d\d|\d\d?)$/;
// Matches IPv6 like [2001:0db8::1]
const ipv6 = /^\[?([a-f0-9:]+)\]?$/i;
return ipv4.test(host) || ipv6.test(host);
}
@@ -45,7 +45,7 @@ export const CaptureView = observer<React.PropsWithChildren<ICaptureViewProps>>(
useHotkeys(
keys,
(event, handler) => {
if (!state.reference?.contains(document.activeElement)) {
if (!event.isTrusted || !state.reference?.contains(document.activeElement)) {
return;
}
@@ -74,7 +74,7 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
onConfigurationFinish: async () => {
const state = this.serverConfigurationFormStateManager.formState;
if (state) {
const saved = await state.save();
const saved = await this.serverConfigurationFormStateManager.save();
if (!saved) {
const error = getFirstException(state.exception);
@@ -7,12 +7,24 @@
*/
import { observer } from 'mobx-react-lite';
import { Group, GroupTitle, Link, IconOrImage, InputField, Switch, Textarea, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import {
Group,
GroupTitle,
Link,
IconOrImage,
InputField,
Switch,
Textarea,
useResource,
useTranslate,
useCustomInputValidation,
} from '@cloudbeaver/core-blocks';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import type { IServerConfigurationPageState } from '../IServerConfigurationPageState.js';
import { MIN_SESSION_EXPIRE_TIME } from './MIN_SESSION_EXPIRE_TIME.js';
import { WEBSITE_LINKS } from '@cloudbeaver/core-links';
import { isIp } from '@cloudbeaver/core-utils';
interface Props {
state: IServerConfigurationPageState;
@@ -21,6 +33,15 @@ interface Props {
export const ServerConfigurationInfoForm = observer<Props>(function ServerConfigurationInfoForm({ state }) {
const serverConfigLoader = useResource(ServerConfigurationInfoForm, ServerConfigResource, undefined);
const translate = useTranslate();
const validation = useCustomInputValidation<string, HTMLTextAreaElement>(value => {
const currentHost = window.location.host;
if (!isIp(window.location.hostname) && value.trim() && !value.includes(currentHost)) {
return translate('administration_configuration_wizard_configuration_supported_hosts_warning', undefined, { host: currentHost });
}
return null;
});
function constructSupportedHostsExample() {
const exampleWithPort = serverConfigLoader.data?.distributed ? 'localhost' : 'localhost:5000';
@@ -35,6 +56,7 @@ export const ServerConfigurationInfoForm = observer<Props>(function ServerConfig
{translate('administration_configuration_wizard_configuration_server_name')}
</InputField>
<Textarea
ref={validation}
title={translate('administration_configuration_wizard_configuration_server_url_description')}
name="supportedHosts"
rows={3}
@@ -11,8 +11,9 @@ import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections';
import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { DefaultNavigatorSettingsResource, PasswordPolicyResource, ProductInfoResource, ServerConfigResource } from '@cloudbeaver/core-root';
import { FormPart, type IFormState } from '@cloudbeaver/core-ui';
import { isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
import { FormPart, formValidationContext, type IFormState } from '@cloudbeaver/core-ui';
import { isIp, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { MIN_SESSION_EXPIRE_TIME } from './Form/MIN_SESSION_EXPIRE_TIME.js';
import type { IServerConfigurationFormPartState } from './IServerConfigurationFormPartState.js';
@@ -51,6 +52,7 @@ export class ServerConfigurationFormPart extends FormPart<IServerConfigurationFo
private readonly authProvidersResource: AuthProvidersResource,
private readonly passwordPolicyResource: PasswordPolicyResource,
private readonly passwordPolicyService: PasswordPolicyService,
private readonly localizationService: LocalizationService,
) {
super(formState, DEFAULT_STATE_GETTER());
}
@@ -67,6 +69,19 @@ export class ServerConfigurationFormPart extends FormPart<IServerConfigurationFo
data: IFormState<IServerConfigurationFormPartState>,
contexts: IExecutionContextProvider<IFormState<IServerConfigurationFormPartState>>,
) {
const validation = contexts.getContext(formValidationContext);
const supportedHosts = this.state.serverConfig.supportedHosts;
const currentHost = window.location.host;
if (!isIp(window.location.hostname) && supportedHosts.trim() && !supportedHosts.includes(currentHost)) {
validation.error(
this.localizationService.translate('administration_configuration_wizard_configuration_supported_hosts_warning', undefined, {
host: currentHost,
}),
);
}
if (this.administrationScreenService.isConfigurationMode) {
await this.authProvidersResource.load(CachedMapAllKey);
@@ -8,6 +8,7 @@
import { makeObservable, observable } from 'mobx';
import { injectable, IServiceProvider } from '@cloudbeaver/core-di';
import type { IFormState } from '@cloudbeaver/core-ui';
import { ServerConfigurationFormService } from './ServerConfigurationFormService.js';
import { ServerConfigurationFormState } from './ServerConfigurationFormState.js';
@@ -27,7 +28,7 @@ export class ServerConfigurationFormStateManager {
});
}
create() {
create(): IFormState<null> {
if (this.formState) {
return this.formState;
}
@@ -36,7 +37,15 @@ export class ServerConfigurationFormStateManager {
return this.formState;
}
destroy() {
async save(): Promise<boolean> {
if (!this.formState) {
return false;
}
return await this.formState.save();
}
destroy(): void {
if (this.formState) {
this.formState?.dispose();
this.formState = null;
@@ -88,7 +88,7 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
}
}
const saved = await formState.save();
const saved = await serverConfigurationFormStateManager.save();
if (!saved) {
const error = getFirstException(part.exception);
@@ -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.
@@ -10,6 +10,7 @@ import { AuthProvidersResource, PasswordPolicyService } from '@cloudbeaver/core-
import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-data-context';
import { DefaultNavigatorSettingsResource, PasswordPolicyResource, ProductInfoResource, ServerConfigResource } from '@cloudbeaver/core-root';
import type { IFormState } from '@cloudbeaver/core-ui';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { ServerConfigurationFormPart } from './ServerConfigurationFormPart.js';
@@ -25,6 +26,7 @@ export function getServerConfigurationFormPart(formState: IFormState<null>): Ser
const authProvidersResource = di.getService(AuthProvidersResource);
const passwordPolicyResource = di.getService(PasswordPolicyResource);
const passwordPolicyService = di.getService(PasswordPolicyService);
const localizationService = di.getService(LocalizationService);
return new ServerConfigurationFormPart(
formState,
@@ -35,6 +37,7 @@ export function getServerConfigurationFormPart(formState: IFormState<null>): Ser
authProvidersResource,
passwordPolicyResource,
passwordPolicyService,
localizationService,
);
});
}
@@ -41,6 +41,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'You can specify multiple server URLs separated by a new line. An empty value means that all URLs are allowed',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_wizard_configuration_server_info', 'Server Information'],
['administration_configuration_wizard_configuration_server_name', 'Server Name'],
@@ -41,6 +41,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'You can specify multiple server URLs separated by a new line. An empty value means that all URLs are allowed',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_wizard_configuration_server_info', 'Informations sur le serveur'],
['administration_configuration_wizard_configuration_server_name', 'Nom du serveur'],
@@ -41,6 +41,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'You can specify multiple server URLs separated by a new line. An empty value means that all URLs are allowed',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_wizard_configuration_server_info', 'Informazioni sul Server'],
['administration_configuration_wizard_configuration_server_name', 'Nome del Server'],
@@ -20,6 +20,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'Вы можете указать несколько URL серверов, разделенных новой строкой. Пустое значение означает, что все URL разрешены',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_wizard_configuration_server_info', 'Информация о сервере'],
['administration_configuration_wizard_configuration_server_name', 'Название сервера'],
@@ -32,6 +32,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'You can specify multiple server URLs separated by a new line. An empty value means that all URLs are allowed',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_tools_save_tooltip', 'Lưu cấu hình'],
['administration_configuration_tools_cancel_tooltip', 'Đặt lại thay đổi'],
['administration_configuration_wizard_configuration_server_info', 'Thông tin Server'],
@@ -38,6 +38,10 @@ export default [
'administration_configuration_wizard_configuration_supported_hosts_description',
'You can specify multiple server URLs separated by a new line. An empty value means that all URLs are allowed',
],
[
'administration_configuration_wizard_configuration_supported_hosts_warning',
'You cannot remove your current domain ({arg:host}). Open the server configuration from another allowed domain or IP-address to remove this domain.',
],
['administration_configuration_wizard_configuration_server_info', '服务器信息'],
['administration_configuration_wizard_configuration_server_name', '服务器名称'],