From cae7699a48cd97d36326007904b57e9b41fd0837 Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:12:49 +0100 Subject: [PATCH] Cb 4280 refactor cb application configuration logic (#2242) * CB-4280 prettify reading configuration file * CB-4280 serialize db config, remove it from te * CB-4280 extract configs from app classes * CB-4280 resolve conflicts * CB-4280 fixes after review * CB-4280 fix copyrights * CB-4280 add comments * CB-4280 resolve conflicts --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- .../BaseServerConfigurationController.java | 34 + .../model/app/BaseWebApplication.java | 23 +- .../cloudbeaver/model/app/WebApplication.java | 2 + .../model/app/WebServerConfiguration.java | 30 + .../app/WebServerConfigurationController.java | 34 + .../src/io/cloudbeaver/utils/WebAppUtils.java | 2 +- .../io/cloudbeaver/model/WebServerConfig.java | 10 +- .../io/cloudbeaver/server/CBApplication.java | 720 ++---------------- .../cloudbeaver/server/CBApplicationCE.java | 28 +- .../io/cloudbeaver/server/CBConstants.java | 1 + .../src/io/cloudbeaver/server/CBPlatform.java | 4 +- .../io/cloudbeaver/server/CBServerConfig.java | 187 +++++ .../CBServerConfigurationController.java | 592 ++++++++++++++ ...ServerConfigurationControllerEmbedded.java | 154 ++++ .../server/jetty/CBJettyServer.java | 35 +- .../server/servlets/CBStaticServlet.java | 6 +- .../EmbeddedSecurityControllerFactory.java | 39 +- .../service/security/db/CBDatabase.java | 4 +- ...baseConfig.java => WebDatabaseConfig.java} | 2 +- .../internal/utils/DBConfigurationUtils.java | 8 +- 20 files changed, 1176 insertions(+), 739 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServerConfigurationController.java create mode 100644 server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfiguration.java create mode 100644 server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfigurationController.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfig.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationControllerEmbedded.java rename server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/{CBDatabaseConfig.java => WebDatabaseConfig.java} (96%) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServerConfigurationController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServerConfigurationController.java new file mode 100644 index 0000000000..8204ffda61 --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServerConfigurationController.java @@ -0,0 +1,34 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.model.app; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Abstract class that contains methods for loading configuration with gson. + */ +public abstract class BaseServerConfigurationController implements WebServerConfigurationController { + + protected Gson getGson() { + return getGsonBuilder().create(); + } + + protected abstract GsonBuilder getGsonBuilder(); + + public abstract T getServerConfiguration(); +} diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java index 96a210154d..d5c584b96b 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java @@ -16,8 +16,6 @@ */ package io.cloudbeaver.model.app; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import io.cloudbeaver.DataSourceFilter; import io.cloudbeaver.WebProjectImpl; import io.cloudbeaver.WebSessionProjectImpl; @@ -102,8 +100,7 @@ public boolean isMultiuser() { return true; } - @Nullable - protected Path loadServerConfiguration() throws DBException { + protected boolean loadServerConfiguration() throws DBException { Path configFilePath = getMainConfigurationFilePath().toAbsolutePath(); Path configFolder = configFilePath.getParent(); @@ -120,13 +117,13 @@ protected Path loadServerConfiguration() throws DBException { // Load config file log.debug("Loading configuration from " + configFilePath); try { - loadConfiguration(configFilePath); + getServerConfigurationController().loadServerConfiguration(configFilePath); } catch (Exception e) { log.error("Error parsing configuration", e); - return null; + return false; } - return configFilePath; + return true; } @Nullable @@ -153,7 +150,7 @@ public Path getLogbackConfigPath() { return getLogbackConfigPath(configFolder); } - private Path getMainConfigurationFilePath() { + protected Path getMainConfigurationFilePath() { String configPath = DEFAULT_CONFIG_FILE_PATH; String[] args = Platform.getCommandLineArgs(); @@ -179,8 +176,6 @@ private Path getCustomConfigPath(Path configPath, String fileName) { return Files.exists(customConfigPath) ? customConfigPath : configPath.resolve(fileName); } - protected abstract void loadConfiguration(Path configPath) throws DBException; - @Override public WebProjectImpl createProjectImpl( @NotNull WebSession webSession, @@ -207,7 +202,7 @@ public DBSSecretController getSecretController( return VoidSecretController.INSTANCE; } - protected static Map getServerConfigProps(Map configProps) { + public static Map getServerConfigProps(Map configProps) { return JSONUtils.getObject(configProps, "server"); } @@ -270,11 +265,7 @@ public WSEventController getEventController() { return null; } - protected Gson getGson() { - return getGsonBuilder().create(); - } - - protected abstract GsonBuilder getGsonBuilder(); + public abstract WebServerConfigurationController getServerConfigurationController(); @Override public boolean isEnvironmentVariablesAccessible() { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java index 4dcee40c12..90c29aaf7f 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java @@ -43,6 +43,8 @@ public interface WebApplication extends DBPApplication { WebAppConfiguration getAppConfiguration(); + WebServerConfiguration getServerConfiguration(); + Path getDataDirectory(boolean create); Path getWorkspaceDirectory(); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfiguration.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfiguration.java new file mode 100644 index 0000000000..1c8fa96c2c --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfiguration.java @@ -0,0 +1,30 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.model.app; + +/** + * Web server configuration. + * Contains only server configuration properties. + */ +public interface WebServerConfiguration { + boolean isDevelMode(); + + default String getRootURI() { + return ""; + } + +} diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfigurationController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfigurationController.java new file mode 100644 index 0000000000..febbce7a4d --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebServerConfigurationController.java @@ -0,0 +1,34 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.model.app; + +import org.jkiss.dbeaver.DBException; + +import java.nio.file.Path; + +/** + * Server configuration controller. + * Works with app server configuration (loads, updates) + */ +public interface WebServerConfigurationController { + + /** + * Loads server configuration. + */ + void loadServerConfiguration(Path configPath) throws DBException; + +} diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java index 52cbb2a836..3041b83f1e 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java @@ -176,7 +176,7 @@ public static void addResponseCookie(HttpServletRequest request, HttpServletResp sessionCookie.setMaxAge((int) (maxSessionIdleTime / 1000)); } - String path = getWebApplication().getRootURI(); + String path = getWebApplication().getServerConfiguration().getRootURI(); if (sameSite != null) { if (!request.isSecure()) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index ea429a9529..c0fefa9ced 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -46,7 +46,7 @@ public WebServerConfig(CBApplication application) { @Property public String getName() { - return CommonUtils.notEmpty(application.getServerName()); + return CommonUtils.notEmpty(application.getServerConfiguration().getServerName()); } @Property @@ -61,12 +61,12 @@ public String getWorkspaceId() { @Property public String getServerURL() { - return CommonUtils.notEmpty(application.getServerURL()); + return CommonUtils.notEmpty(application.getServerConfiguration().getServerURL()); } @Property public String getRootURI() { - return CommonUtils.notEmpty(application.getRootURI()); + return CommonUtils.notEmpty(application.getServerConfiguration().getRootURI()); } @Deprecated @@ -127,7 +127,7 @@ public boolean isConfigurationMode() { @Property public boolean isDevelopmentMode() { - return application.isDevelMode(); + return application.getServerConfiguration().isDevelMode(); } @Property @@ -142,7 +142,7 @@ public boolean isResourceManagerEnabled() { @Property public long getSessionExpireTime() { - return application.getConfiguredMaxSessionIdleTime(); + return application.getServerConfiguration().getMaxSessionIdleTime(); } @Property diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index 0b1653bf36..cc8e837009 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -16,11 +16,7 @@ */ package io.cloudbeaver.server; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.InstanceCreator; import io.cloudbeaver.WebServiceUtils; -import io.cloudbeaver.auth.CBAuthConstants; import io.cloudbeaver.auth.NoAuthCredentialsProvider; import io.cloudbeaver.model.app.BaseWebApplication; import io.cloudbeaver.model.app.WebAuthApplication; @@ -31,10 +27,8 @@ import io.cloudbeaver.service.DBWServiceInitializer; import io.cloudbeaver.service.DBWServiceServerConfigurator; import io.cloudbeaver.service.security.CBEmbeddedSecurityController; -import io.cloudbeaver.service.security.PasswordPolicyConfiguration; import io.cloudbeaver.service.security.SMControllerConfiguration; import io.cloudbeaver.service.session.WebSessionManager; -import io.cloudbeaver.utils.WebAppUtils; import io.cloudbeaver.utils.WebDataSourceUtils; import org.eclipse.core.runtime.Platform; import org.eclipse.osgi.service.datalocation.Location; @@ -42,54 +36,45 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; -import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.model.DBPDataSourceContainer; -import org.jkiss.dbeaver.model.app.DBPApplication; import org.jkiss.dbeaver.model.app.DBPPlatform; import org.jkiss.dbeaver.model.auth.AuthInfo; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; import org.jkiss.dbeaver.model.connection.DBPDriver; import org.jkiss.dbeaver.model.data.json.JSONUtils; -import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.security.SMAdminController; -import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; import org.jkiss.dbeaver.model.security.SMConstants; import org.jkiss.dbeaver.model.security.SMObjectType; import org.jkiss.dbeaver.model.websocket.event.WSEventController; import org.jkiss.dbeaver.model.websocket.event.WSServerConfigurationChangedEvent; import org.jkiss.dbeaver.registry.BaseApplicationImpl; -import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings; import org.jkiss.dbeaver.runtime.DBWorkbench; -import org.jkiss.dbeaver.runtime.IVariableResolver; import org.jkiss.dbeaver.runtime.ui.DBPPlatformUI; -import org.jkiss.dbeaver.utils.ContentUtils; import org.jkiss.dbeaver.utils.GeneralUtils; -import org.jkiss.dbeaver.utils.PrefUtils; import org.jkiss.dbeaver.utils.SystemVariablesResolver; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.StandardConstants; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.security.Permission; import java.security.Policy; import java.security.ProtectionDomain; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * This class controls all aspects of the application's execution */ -public abstract class CBApplication extends BaseWebApplication implements WebAuthApplication { +public abstract class CBApplication extends BaseWebApplication implements WebAuthApplication { private static final Log log = Log.getLog(CBApplication.class); @@ -106,41 +91,15 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut Log.setDefaultDebugStream(System.out); } - private String staticContent = ""; public static CBApplication getInstance() { return (CBApplication) BaseApplicationImpl.getInstance(); } - - protected String serverURL; - protected int serverPort = CBConstants.DEFAULT_SERVER_PORT; - private String serverHost = null; - private String serverName = null; - private String sslConfigurationPath = null; - private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT; - private String rootURI = CBConstants.DEFAULT_ROOT_URI; - private String servicesURI = CBConstants.DEFAULT_SERVICES_URI; - - private String workspaceLocation = CBConstants.DEFAULT_WORKSPACE_LOCATION; - private String driversLocation = CBConstants.DEFAULT_DRIVERS_LOCATION; private File homeDirectory; - // Configurations - protected final Map productConfiguration = new HashMap<>(); - protected final Map databaseConfiguration = new HashMap<>(); - protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration(); - private final CBAppConfig appConfiguration = new CBAppConfig(); - private Map externalProperties = new LinkedHashMap<>(); - private Map originalConfigurationProperties = new LinkedHashMap<>(); - // Persistence protected SMAdminController securityController; - - private long maxSessionIdleTime = CBAuthConstants.MAX_SESSION_IDLE_TIME; - - private boolean develMode = false; private boolean configurationMode = false; - private boolean enableSecurityManager = false; private String localHostAddress; protected String containerId; private final List localInetAddresses = new ArrayList<>(); @@ -153,39 +112,32 @@ public CBApplication() { } public String getServerURL() { - return serverURL; + return getServerConfiguration().getServerURL(); } // Port this server listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort() @Override public int getServerPort() { - return serverPort; + return getServerConfiguration().getServerPort(); } // The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0, then bind to all interfaces. public String getServerHost() { - return serverHost; + return getServerConfiguration().getServerHost(); } public String getServerName() { - return serverName; - } - - public String getContentRoot() { - return contentRoot; + return getServerConfiguration().getServerName(); } public String getRootURI() { - return rootURI; + return getServerConfiguration().getRootURI(); } public String getServicesURI() { - return servicesURI; + return getServerConfiguration().getServicesURI(); } - public String getDriversLocation() { - return driversLocation; - } public Path getHomeDirectory() { return homeDirectory.toPath(); @@ -203,58 +155,49 @@ public long getMaxSessionIdleTime() { if (isConfigurationMode()) { return CONFIGURATION_MODE_SESSION_IDLE_TIME; } - return maxSessionIdleTime; + return getServerConfiguration().getMaxSessionIdleTime(); } /** * @return max session idle time from server configuration, may differ from {@link #getMaxSessionIdleTime()} */ - public long getConfiguredMaxSessionIdleTime() { - return maxSessionIdleTime; - } public CBAppConfig getAppConfiguration() { - return appConfiguration; + return getServerConfigurationController().getAppConfiguration(); + } + + public T getServerConfiguration() { + return getServerConfigurationController().getServerConfiguration(); } @Override public WebAuthConfiguration getAuthConfiguration() { - return appConfiguration; + return getAppConfiguration(); } @Override public String getAuthServiceURL() { - return Stream.of(getServerURL(), getRootURI(), getServicesURI()) - .map(WebAppUtils::removeSideSlashes) - .filter(CommonUtils::isNotEmpty) - .collect(Collectors.joining("/")); + return getServerConfigurationController().getAuthServiceURL(); } public Map getProductConfiguration() { - return productConfiguration; + return getServerConfigurationController().getProductConfiguration(); } public SMControllerConfiguration getSecurityManagerConfiguration() { - return securityManagerConfiguration; + return getServerConfigurationController().getSecurityManagerConfiguration(); } public SMAdminController getSecurityController() { return securityController; } - @Override - public boolean isHeadlessMode() { - return true; - } - @Override protected void startServer() { CBPlatform.setApplication(this); - - Path configPath; + initHomeFolder(); try { - configPath = loadServerConfiguration(); - if (configPath == null) { + if (!loadServerConfiguration()) { return; } } catch (DBException e) { @@ -263,7 +206,7 @@ protected void startServer() { } refreshDisabledDriversConfig(); - configurationMode = CommonUtils.isEmpty(serverName); + configurationMode = CommonUtils.isEmpty(getServerConfiguration().getServerName()); eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on @@ -286,11 +229,11 @@ protected void startServer() { URL wsLocationURL = new URL( "file", //$NON-NLS-1$ null, - workspaceLocation); + getServerConfiguration().getWorkspaceLocation()); instanceLoc.set(wsLocationURL, true); } } catch (Exception e) { - log.error("Error setting workspace location to " + workspaceLocation, e); + log.error("Error setting workspace location to " + getServerConfiguration().getWorkspaceLocation(), e); return; } @@ -303,15 +246,15 @@ protected void startServer() { log.debug("\tGlobal workspace: '" + instanceLoc.getURL() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ log.debug("\tMemory available " + (runtime.totalMemory() / (1024 * 1024)) + "Mb/" + (runtime.maxMemory() / (1024 * 1024)) + "Mb"); - DBPApplication application = DBWorkbench.getPlatform().getApplication(); + DBWorkbench.getPlatform().getApplication(); - log.debug("\tContent root: " + new File(contentRoot).getAbsolutePath()); - log.debug("\tDrivers storage: " + new File(driversLocation).getAbsolutePath()); + log.debug("\tContent root: " + new File(getServerConfiguration().getContentRoot()).getAbsolutePath()); + log.debug("\tDrivers storage: " + new File(getServerConfiguration().getDriversLocation()).getAbsolutePath()); //log.debug("\tDrivers root: " + driversLocation); //log.debug("\tProduct details: " + application.getInfoDetails()); - log.debug("\tListen port: " + serverPort + (CommonUtils.isEmpty(serverHost) ? " on all interfaces" : " on " + serverHost)); - log.debug("\tBase URI: " + servicesURI); - if (develMode) { + log.debug("\tListen port: " + getServerPort() + (CommonUtils.isEmpty(getServerHost()) ? " on all interfaces" : " on " + getServerHost())); + log.debug("\tBase URI: " + getServicesURI()); + if (isDevelMode()) { log.debug("\tDevelopment mode"); } else { log.debug("\tProduction mode"); @@ -356,15 +299,16 @@ protected void startServer() { if (configurationMode) { // Try to configure automatically - performAutoConfiguration(configPath.toFile().getParentFile()); + performAutoConfiguration(getMainConfigurationFilePath().toFile().getParentFile()); } else if (!isMultiNode()) { + var appConfiguration = getServerConfigurationController().getAppConfiguration(); if (appConfiguration.isGrantConnectionsAccessToAnonymousTeam()) { grantAnonymousAccessToConnections(appConfiguration, CBConstants.ADMIN_AUTO_GRANT); } grantPermissionsToConnections(); } - if (enableSecurityManager) { + if (getServerConfiguration().isEnableSecurityManager()) { Policy.setPolicy(new Policy() { @Override public boolean implies(ProtectionDomain domain, Permission permission) { @@ -444,7 +388,7 @@ protected void performAutoConfiguration(File configPath) { autoAdminName, autoAdminPassword, Collections.emptyList(), - maxSessionIdleTime, + getMaxSessionIdleTime(), getAppConfiguration(), null ); @@ -499,19 +443,9 @@ private void determineLocalAddresses() { } - @NotNull - private File getRuntimeAppConfigFile() { - return getDataDirectory(true).resolve(CBConstants.RUNTIME_APP_CONFIG_FILE_NAME).toFile(); - } - - @NotNull - private Path getRuntimeProductConfigFilePath() { - return getDataDirectory(false).resolve(CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME); - } - @NotNull public Path getDataDirectory(boolean create) { - File dataDir = new File(workspaceLocation, CBConstants.RUNTIME_DATA_DIR_NAME); + File dataDir = new File(getServerConfiguration().getWorkspaceLocation(), CBConstants.RUNTIME_DATA_DIR_NAME); if (create && !dataDir.exists()) { if (!dataDir.mkdirs()) { log.error("Can't create data directory '" + dataDir.getAbsolutePath() + "'"); @@ -522,7 +456,7 @@ public Path getDataDirectory(boolean create) { @Override public Path getWorkspaceDirectory() { - return Path.of(workspaceLocation); + return Path.of(getServerConfiguration().getWorkspaceLocation()); } private void initializeSecurityController() throws DBException { @@ -531,147 +465,6 @@ private void initializeSecurityController() throws DBException { protected abstract SMAdminController createGlobalSecurityController() throws DBException; - @Nullable - @Override - protected Path loadServerConfiguration() throws DBException { - initHomeFolder(); - Path path = super.loadServerConfiguration(); - - - File runtimeConfigFile = getRuntimeAppConfigFile(); - if (runtimeConfigFile.exists()) { - log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]"); - parseConfiguration(runtimeConfigFile); - } - return path; - } - - @Override - protected void loadConfiguration(Path configPath) throws DBException { - log.debug("Using configuration [" + configPath + "]"); - - if (!Files.exists(configPath)) { - log.error("Configuration file " + configPath + " doesn't exist. Use defaults."); - } else { - parseConfiguration(configPath.toFile()); - } - // Set default preferences - PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(), - ModelPreferences.UI_DRIVERS_HOME, - getDriversLocation()); - } - - private void parseConfiguration(File configFile) throws DBException { - Map configProps = readConfiguration(configFile); - parseConfiguration(configProps); - } - - protected void parseConfiguration(Map configProps) throws DBException { - Path homeFolder = getHomeDirectory(); - CBAppConfig prevConfig = new CBAppConfig(appConfiguration); - Gson gson = getGson(); - try { - Map serverConfig = JSONUtils.getObject(configProps, "server"); - serverPort = JSONUtils.getInteger(serverConfig, CBConstants.PARAM_SERVER_PORT, serverPort); - serverHost = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_HOST, serverHost); - if (serverConfig.containsKey(CBConstants.PARAM_SERVER_URL)) { - serverURL = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_URL, serverURL); - } else if (serverURL == null) { - String hostName = serverHost; - if (CommonUtils.isEmpty(hostName)) { - try { - hostName = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.debug("Error resolving localhost address: " + e.getMessage()); - hostName = HOST_LOCALHOST; - } - } - serverURL = "http://" + hostName + ":" + serverPort; - } - - serverName = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_NAME, serverName); - sslConfigurationPath = JSONUtils.getString(serverConfig, CBConstants.PARAM_SSL_CONFIGURATION_PATH, sslConfigurationPath); - contentRoot = WebAppUtils.getRelativePath( - JSONUtils.getString(serverConfig, CBConstants.PARAM_CONTENT_ROOT, contentRoot), homeFolder); - rootURI = readRootUri(serverConfig); - servicesURI = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVICES_URI, servicesURI); - driversLocation = WebAppUtils.getRelativePath( - JSONUtils.getString(serverConfig, CBConstants.PARAM_DRIVERS_LOCATION, driversLocation), homeFolder); - workspaceLocation = WebAppUtils.getRelativePath( - JSONUtils.getString(serverConfig, CBConstants.PARAM_WORKSPACE_LOCATION, workspaceLocation), homeFolder); - - maxSessionIdleTime = JSONUtils.getLong(serverConfig, - CBConstants.PARAM_SESSION_EXPIRE_PERIOD, - maxSessionIdleTime); - - develMode = JSONUtils.getBoolean(serverConfig, CBConstants.PARAM_DEVEL_MODE, develMode); - enableSecurityManager = JSONUtils.getBoolean(serverConfig, - CBConstants.PARAM_SECURITY_MANAGER, - enableSecurityManager); - //SM config - gson.fromJson( - gson.toJson(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)), - SMControllerConfiguration.class - ); - // App config - Map appConfig = JSONUtils.getObject(configProps, "app"); - validateConfiguration(appConfig); - gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class); - - databaseConfiguration.putAll(JSONUtils.getObject(serverConfig, CBConstants.PARAM_DB_CONFIGURATION)); - - readProductConfiguration(serverConfig, gson, homeFolder.toString()); - - String staticContentsFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_STATIC_CONTENT); - if (!CommonUtils.isEmpty(staticContentsFile)) { - try { - staticContent = Files.readString(Path.of(staticContentsFile)); - } catch (IOException e) { - log.error("Error reading static contents from " + staticContentsFile, e); - } - } - parseAdditionalConfiguration(configProps); - } catch (Exception e) { - throw new DBException("Error parsing server configuration", e); - } - - // Backward compatibility: load configs map - appConfiguration.loadLegacyCustomConfigs(); - - // Merge new config with old one - mergeOldConfiguration(prevConfig); - - patchConfigurationWithProperties(productConfiguration); - } - - private String readRootUri(Map serverConfig) { - String uri = JSONUtils.getString(serverConfig, CBConstants.PARAM_ROOT_URI, rootURI); - //slashes are needed to correctly display static resources on ui - if (!uri.endsWith("/")) { - uri = uri + '/'; - } - if (!uri.startsWith("/")) { - uri = '/' + uri; - } - return uri; - } - - protected void mergeOldConfiguration(CBAppConfig prevConfig) { - Map mergedPlugins = Stream.concat( - prevConfig.getPlugins().entrySet().stream(), - appConfiguration.getPlugins().entrySet().stream() - ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (o, o2) -> o2)); - appConfiguration.setPlugins(mergedPlugins); - - Set mergedAuthProviders = Stream.concat( - prevConfig.getAuthCustomConfigurations().stream(), - appConfiguration.getAuthCustomConfigurations().stream() - ) - .collect(Collectors.toCollection(LinkedHashSet::new)); - appConfiguration.setAuthProvidersConfigurations(mergedAuthProviders); - } - @NotNull protected String initHomeFolder() { String homeFolder = System.getenv(CBConstants.ENV_CB_HOME); @@ -689,130 +482,12 @@ protected void validateConfiguration(Map appConfig) throws DBExc } - protected void readProductConfiguration(Map serverConfig, Gson gson, String homeFolder) - throws DBException { - String productConfigPath = WebAppUtils.getRelativePath( - JSONUtils.getString( - serverConfig, - CBConstants.PARAM_PRODUCT_CONFIGURATION, - CBConstants.DEFAULT_PRODUCT_CONFIGURATION - ), - homeFolder - ); - - if (!CommonUtils.isEmpty(productConfigPath)) { - File productConfigFile = new File(productConfigPath); - if (!productConfigFile.exists()) { - log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'"); - } else { - log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'"); - try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile), - StandardCharsets.UTF_8)) { - productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); - } catch (Exception e) { - throw new DBException("Error reading product configuration", e); - } - } - } - - // Add product config from runtime - File rtConfig = getRuntimeProductConfigFilePath().toFile(); - if (rtConfig.exists()) { - log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'"); - try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) { - productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); - Map flattenConfig = WebAppUtils.flattenMap(this.productConfiguration); - this.productConfiguration.clear(); - this.productConfiguration.putAll(flattenConfig); - } catch (Exception e) { - throw new DBException("Error reading product runtime configuration", e); - } - } - } - - protected Map readConnectionsPermissionsConfiguration(Path parentPath) { - String permissionsConfigPath = WebAppUtils.getRelativePath(CBConstants.DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION, - parentPath); - File permissionsConfigFile = new File(permissionsConfigPath); - if (permissionsConfigFile.exists()) { - log.debug("Load permissions configuration from '" + permissionsConfigFile.getAbsolutePath() + "'"); - try (Reader reader = new InputStreamReader(new FileInputStream(permissionsConfigFile), - StandardCharsets.UTF_8)) { - return JSONUtils.parseMap(getGson(), reader); - } catch (Exception e) { - log.error("Error reading permissions configuration", e); - } - } - return null; - } - - protected Map readConfiguration(File configFile) throws DBException { - Map configProps = new LinkedHashMap<>(); - if (configFile.exists()) { - log.debug("Read configuration [" + configFile.getAbsolutePath() + "]"); - // saves original configuration file - this.originalConfigurationProperties.putAll(readConfigurationFile(configFile)); - - configProps.putAll(readConfigurationFile(configFile)); - patchConfigurationWithProperties(configProps); // patch original properties - } - - readAdditionalConfiguration(configProps); - if (configProps.isEmpty()) { - return Map.of(); - } - - Map serverConfig = getServerConfigProps(configProps); - String externalPropertiesFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_EXTERNAL_PROPERTIES); - if (!CommonUtils.isEmpty(externalPropertiesFile)) { - Properties props = new Properties(); - try (InputStream is = Files.newInputStream(Path.of(externalPropertiesFile))) { - props.load(is); - } catch (IOException e) { - log.error("Error loading external properties from " + externalPropertiesFile, e); - } - for (String propName : props.stringPropertyNames()) { - this.externalProperties.put(propName, props.getProperty(propName)); - } - } - - patchConfigurationWithProperties(configProps); // patch again because properties can be changed - return configProps; - } - - public Map readConfigurationFile(File configFile) throws DBException { - try (Reader reader = new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8)) { - return JSONUtils.parseMap(getGson(), reader); - } catch (Exception e) { - throw new DBException("Error parsing server configuration", e); - } - } - - protected GsonBuilder getGsonBuilder() { - // Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431) - InstanceCreator appConfigCreator = type -> appConfiguration; - InstanceCreator navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings(); - InstanceCreator smConfigCreator = type -> securityManagerConfiguration; - InstanceCreator smPasswordPoliceConfigCreator = - type -> securityManagerConfiguration.getPasswordPolicyConfiguration(); - return new GsonBuilder() - .setLenient() - .registerTypeAdapter(CBAppConfig.class, appConfigCreator) - .registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator) - .registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator) - .registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator); - } - - protected void readAdditionalConfiguration(Map rootConfig) throws DBException { - - } - - protected void parseAdditionalConfiguration(Map serverConfig) throws DBException { - - } - private void runWebServer() { - log.debug("Starting Jetty server (" + serverPort + " on " + (CommonUtils.isEmpty(serverHost) ? "all interfaces" : serverHost) + ") "); + log.debug( + String.format("Starting Jetty server (%d on %s) ", + getServerPort(), + CommonUtils.isEmpty(getServerHost()) ? "all interfaces" : getServerHost()) + ); new CBJettyServer(this).runServer(); } @@ -837,7 +512,7 @@ public String getDefaultProjectName() { } public boolean isDevelMode() { - return develMode; + return getServerConfiguration().isDevelMode(); } public boolean isConfigurationMode() { @@ -881,31 +556,32 @@ public synchronized void finishConfiguration( // Save runtime configuration log.debug("Saving runtime configuration"); - saveRuntimeConfig(newServerName, newServerURL, sessionExpireTime, appConfig, credentialsProvider); + getServerConfigurationController().saveRuntimeConfig(newServerName, newServerURL, sessionExpireTime, appConfig, credentialsProvider); // Grant permissions to predefined connections if (appConfig.isGrantConnectionsAccessToAnonymousTeam()) { grantAnonymousAccessToConnections(appConfig, adminName); } + reloadConfiguration(credentialsProvider); + } + public synchronized void reloadConfiguration(@Nullable SMCredentialsProvider credentialsProvider) throws DBException { // Re-load runtime configuration try { + Path runtimeAppConfigPath = getServerConfigurationController().getRuntimeAppConfigPath(); log.debug("Reloading application configuration"); - Map runtimeConfigProps = readRuntimeConfigurationProperties(); - if (!runtimeConfigProps.isEmpty()) { - parseConfiguration(runtimeConfigProps); - } + getServerConfigurationController().loadConfiguration(runtimeAppConfigPath); } catch (Exception e) { throw new DBException("Error parsing configuration", e); } - configurationMode = CommonUtils.isEmpty(serverName); + configurationMode = CommonUtils.isEmpty(getServerName()); // Reloading configuration by services for (DBWServiceServerConfigurator wsc : WebServiceRegistry.getInstance() .getWebServices(DBWServiceServerConfigurator.class)) { try { - wsc.reloadConfiguration(appConfig); + wsc.reloadConfiguration(getAppConfiguration()); } catch (Exception e) { log.warn("Error reloading configuration by web service " + wsc.getClass().getName(), e); } @@ -915,11 +591,6 @@ public synchronized void finishConfiguration( eventController.setForceSkipEvents(isConfigurationMode()); } - protected Map readRuntimeConfigurationProperties() throws DBException { - File runtimeConfigFile = getRuntimeAppConfigFile(); - return readConfiguration(runtimeConfigFile); - } - protected abstract void finishSecurityServiceConfiguration( @NotNull String adminName, @Nullable String adminPassword, @@ -927,11 +598,11 @@ protected abstract void finishSecurityServiceConfiguration( ) throws DBException; public synchronized void flushConfiguration(SMCredentialsProvider webSession) throws DBException { - saveRuntimeConfig(serverName, serverURL, maxSessionIdleTime, appConfiguration, webSession); + getServerConfigurationController().saveRuntimeConfig(webSession); } public synchronized void flushConfiguration() throws DBException { - saveRuntimeConfig(serverName, serverURL, maxSessionIdleTime, appConfiguration, new NoAuthCredentialsProvider()); + getServerConfigurationController().saveRuntimeConfig(new NoAuthCredentialsProvider()); } @@ -961,8 +632,9 @@ private void grantAnonymousAccessToConnections(CBAppConfig appConfig, String adm private void grantPermissionsToConnections() { try { var globalRegistry = WebDataSourceUtils.getGlobalDataSourceRegistry(); - var permissionsConfiguration = readConnectionsPermissionsConfiguration(globalRegistry.getProject() - .getMetadataFolder(false)); + var permissionsConfiguration = getServerConfigurationController().readConnectionsPermissionsConfiguration( + globalRegistry.getProject().getMetadataFolder(false)); + if (permissionsConfiguration == null) { return; } @@ -989,222 +661,6 @@ private void grantPermissionsToConnections() { } } - protected void saveRuntimeConfig( - String newServerName, - String newServerURL, - long sessionExpireTime, - CBAppConfig appConfig, - SMCredentialsProvider credentialsProvider - ) throws DBException { - if (newServerName == null) { - throw new DBException("Invalid server configuration, server name cannot be empty"); - } - Map configurationProperties = collectConfigurationProperties(newServerName, - newServerURL, - sessionExpireTime, - appConfig); - validateConfiguration(configurationProperties); - writeRuntimeConfig(getRuntimeAppConfigFile(), configurationProperties); - } - - private void writeRuntimeConfig(File runtimeConfigFile, Map configurationProperties) throws DBException { - if (runtimeConfigFile.exists()) { - ContentUtils.makeFileBackup(runtimeConfigFile.toPath()); - } - - try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) { - Gson gson = new GsonBuilder() - .setLenient() - .setPrettyPrinting() - .create(); - gson.toJson(configurationProperties, out); - - } catch (IOException e) { - throw new DBException("Error writing runtime configuration", e); - } - } - - protected Map collectConfigurationProperties( - String newServerName, - String newServerURL, - long sessionExpireTime, - CBAppConfig appConfig - ) { - Map rootConfig = new LinkedHashMap<>(); - { - var serverConfigProperties = new LinkedHashMap(); - var originServerConfig = getServerConfigProps(this.originalConfigurationProperties); // get server properties from original configuration file - rootConfig.put("server", serverConfigProperties); - collectServerConfigProperties(newServerName, newServerURL, sessionExpireTime, originServerConfig, serverConfigProperties); - } - { - var appConfigProperties = new LinkedHashMap(); - Map oldAppConfig = JSONUtils.getObject(this.originalConfigurationProperties, "app"); - rootConfig.put("app", appConfigProperties); - - copyConfigValue( - oldAppConfig, appConfigProperties, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - "supportsCustomConnections", - appConfig.isSupportsCustomConnections()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - "publicCredentialsSaveEnabled", - appConfig.isPublicCredentialsSaveEnabled()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - "adminCredentialsSaveEnabled", - appConfig.isAdminCredentialsSaveEnabled()); - copyConfigValue( - oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth()); - copyConfigValue( - oldAppConfig, appConfigProperties, "forwardProxy", appConfig.isEnabledForwardProxy()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - "linkExternalCredentialsWithUser", - appConfig.isLinkExternalCredentialsWithUser()); - copyConfigValue( - oldAppConfig, appConfigProperties, "redirectOnFederatedAuth", appConfig.isRedirectOnFederatedAuth()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, - appConfig.isResourceManagerEnabled()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - CBConstants.PARAM_SHOW_READ_ONLY_CONN_INFO, - appConfig.isShowReadOnlyConnectionInfo()); - copyConfigValue( - oldAppConfig, - appConfigProperties, - CBConstants.PARAM_CONN_GRANT_ANON_ACCESS, - appConfig.isGrantConnectionsAccessToAnonymousTeam()); - - Map resourceQuotas = new LinkedHashMap<>(); - Map originResourceQuotas = JSONUtils.getObject(oldAppConfig, - CBConstants.PARAM_RESOURCE_QUOTAS); - for (Map.Entry mp : appConfig.getResourceQuotas().entrySet()) { - copyConfigValue(originResourceQuotas, resourceQuotas, mp.getKey(), mp.getValue()); - } - appConfigProperties.put(CBConstants.PARAM_RESOURCE_QUOTAS, resourceQuotas); - - { - // Save only differences in def navigator settings - DBNBrowseSettings navSettings = appConfig.getDefaultNavigatorSettings(); - var navigatorProperties = new LinkedHashMap(); - appConfigProperties.put("defaultNavigatorSettings", navigatorProperties); - - if (navSettings.isShowSystemObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowSystemObjects()) { - navigatorProperties.put("showSystemObjects", navSettings.isShowSystemObjects()); - } - if (navSettings.isShowUtilityObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowUtilityObjects()) { - navigatorProperties.put("showUtilityObjects", navSettings.isShowUtilityObjects()); - } - if (navSettings.isShowOnlyEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowOnlyEntities()) { - navigatorProperties.put("showOnlyEntities", navSettings.isShowOnlyEntities()); - } - if (navSettings.isMergeEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isMergeEntities()) { - navigatorProperties.put("mergeEntities", navSettings.isMergeEntities()); - } - if (navSettings.isHideFolders() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideFolders()) { - navigatorProperties.put("hideFolders", navSettings.isHideFolders()); - } - if (navSettings.isHideSchemas() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideSchemas()) { - navigatorProperties.put("hideSchemas", navSettings.isHideSchemas()); - } - if (navSettings.isHideVirtualModel() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideVirtualModel()) { - navigatorProperties.put("hideVirtualModel", navSettings.isHideVirtualModel()); - } - } - if (appConfig.getEnabledFeatures() != null) { - appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures())); - } - if (appConfig.getEnabledAuthProviders() != null) { - appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders())); - } - if (appConfig.getEnabledDrivers() != null) { - appConfigProperties.put("enabledDrivers", Arrays.asList(appConfig.getEnabledDrivers())); - } - if (appConfig.getDisabledDrivers() != null) { - appConfigProperties.put("disabledDrivers", Arrays.asList(appConfig.getDisabledDrivers())); - } - - if (!CommonUtils.isEmpty(appConfig.getPlugins())) { - appConfigProperties.put("plugins", appConfig.getPlugins()); - } - if (!CommonUtils.isEmpty(appConfig.getAuthCustomConfigurations())) { - appConfigProperties.put("authConfigurations", appConfig.getAuthCustomConfigurations()); - } - } - return rootConfig; - } - - protected void collectServerConfigProperties( - String newServerName, - String newServerURL, - long sessionExpireTime, - Map originServerConfig, - Map serverConfigProperties - ) { - if (!CommonUtils.isEmpty(newServerName)) { - copyConfigValue(originServerConfig, - serverConfigProperties, - CBConstants.PARAM_SERVER_NAME, - newServerName); - } - if (!CommonUtils.isEmpty(newServerURL)) { - copyConfigValue( - originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, newServerURL); - } - if (sessionExpireTime > 0) { - copyConfigValue( - originServerConfig, - serverConfigProperties, - CBConstants.PARAM_SESSION_EXPIRE_PERIOD, - sessionExpireTime); - } - var databaseConfigProperties = new LinkedHashMap(); - Map oldRuntimeDBConfig = JSONUtils.getObject(originServerConfig, - CBConstants.PARAM_DB_CONFIGURATION); - if (!CommonUtils.isEmpty(databaseConfiguration) && !isDistributed()) { - for (Map.Entry mp : databaseConfiguration.entrySet()) { - copyConfigValue(oldRuntimeDBConfig, databaseConfigProperties, mp.getKey(), mp.getValue()); - } - serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties); - } - savePasswordPolicyConfig(originServerConfig, serverConfigProperties); - } - - private void savePasswordPolicyConfig(Map originServerConfig, Map serverConfigProperties) { - // save password policy configuration - var passwordPolicyProperties = new LinkedHashMap(); - - var oldRuntimePasswordPolicyConfig = JSONUtils.getObject( - JSONUtils.getObject(originServerConfig, CBConstants.PARAM_SM_CONFIGURATION), - CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION - ); - Gson gson = getGson(); - Map passwordPolicyConfig = gson.fromJson( - gson.toJsonTree(securityManagerConfiguration.getPasswordPolicyConfiguration()), - JSONUtils.MAP_TYPE_TOKEN - ); - if (!CommonUtils.isEmpty(passwordPolicyConfig) && !isDistributed()) { - for (Map.Entry mp : passwordPolicyConfig.entrySet()) { - copyConfigValue(oldRuntimePasswordPolicyConfig, passwordPolicyProperties, mp.getKey(), mp.getValue()); - } - serverConfigProperties.put( - CBConstants.PARAM_SM_CONFIGURATION, - Map.of(CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION, passwordPolicyProperties) - ); - } - } - //////////////////////////////////////////////////////////////////////// // License management @@ -1222,30 +678,6 @@ public String getLicenseStatus() { return null; } - /** - * - */ - public String getStaticContent() { - return staticContent; - } - - //////////////////////////////////////////////////////////////////////// - // Configuration utils - - private void patchConfigurationWithProperties(Map configProps) { - IVariableResolver varResolver = new SystemVariablesResolver() { - @Override - public String get(String name) { - String propValue = externalProperties.get(name); - if (propValue != null) { - return propValue; - } - return super.get(name); - } - }; - patchConfigurationWithProperties(configProps, varResolver); - } - public WebSessionManager getSessionManager() { if (sessionManager == null) { sessionManager = createSessionManager(); @@ -1266,27 +698,6 @@ public List getAvailableAuthRoles() { return List.of(); } - // gets info about patterns from original configuration file and saves it to runtime config - private void copyConfigValue( - Map oldConfig, - Map newConfig, - String key, - Object defaultValue - ) { - Object value = oldConfig.get(key); - if (value instanceof Map && defaultValue instanceof Map) { - Map subValue = new LinkedHashMap<>(); - Map oldConfigValue = JSONUtils.getObject(oldConfig, key); - for (Map.Entry entry : oldConfigValue.entrySet()) { - copyConfigValue(oldConfigValue, subValue, entry.getKey(), ((Map) defaultValue).get(entry.getKey())); - } - newConfig.put(key, subValue); - } else { - Object newConfigValue = WebAppUtils.getExtractedValue(oldConfig.get(key), defaultValue); - newConfig.put(key, newConfigValue); - } - } - @Override public WSEventController getEventController() { return eventController; @@ -1304,15 +715,6 @@ public String getContainerId() { return containerId; } - @Nullable - public Path getSslConfigurationPath() { - if (sslConfigurationPath == null) { - return null; - } - var sslConfiguration = Path.of(sslConfigurationPath); - return sslConfiguration.isAbsolute() ? sslConfiguration : getHomeDirectory().resolve(sslConfiguration); - } - @NotNull @Override public Class getPlatformClass() { @@ -1325,10 +727,7 @@ public Class getPlatformUIClass() { } public void saveProductConfiguration(SMCredentialsProvider credentialsProvider, Map productConfiguration) throws DBException { - Map mergedConfig = WebAppUtils.mergeConfigurations(this.productConfiguration, productConfiguration); - writeRuntimeConfig(getRuntimeProductConfigFilePath().toFile(), mergedConfig); - this.productConfiguration.clear(); - this.productConfiguration.putAll(WebAppUtils.flattenMap(mergedConfig)); + getServerConfigurationController().saveProductConfiguration(productConfiguration); sendConfigChangedEvent(credentialsProvider); } @@ -1340,6 +739,9 @@ protected void sendConfigChangedEvent(SMCredentialsProvider credentialsProvider) eventController.addEvent(new WSServerConfigurationChangedEvent(sessionId, null)); } + @Override + public abstract CBServerConfigurationController getServerConfigurationController(); + private void refreshDisabledDriversConfig() { CBAppConfig config = getAppConfiguration(); Set disabledDrivers = new LinkedHashSet<>(Arrays.asList(config.getDisabledDrivers())); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplicationCE.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplicationCE.java index c29d381496..0e732b3b2d 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplicationCE.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplicationCE.java @@ -36,37 +36,45 @@ import java.util.List; -public class CBApplicationCE extends CBApplication { +public class CBApplicationCE extends CBApplication { private static final Log log = Log.getLog(CBApplicationCE.class); + private final CBServerConfigurationControllerEmbedded serverConfigController; + + public CBApplicationCE() { + serverConfigController = new CBServerConfigurationControllerEmbedded<>(new CBServerConfig()); + } + @Override public SMController createSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException { return new EmbeddedSecurityControllerFactory().createSecurityService( this, - databaseConfiguration, + getServerConfiguration().getDatabaseConfiguration(), credentialsProvider, - securityManagerConfiguration + getServerConfigurationController().getSecurityManagerConfiguration() ); } @Override public SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException { return new EmbeddedSecurityControllerFactory().createSecurityService( this, - databaseConfiguration, + getServerConfiguration().getDatabaseConfiguration(), credentialsProvider, - securityManagerConfiguration + getServerConfigurationController().getSecurityManagerConfiguration() ); } protected SMAdminController createGlobalSecurityController() throws DBException { return new EmbeddedSecurityControllerFactory().createSecurityService( this, - databaseConfiguration, + getServerConfiguration().getDatabaseConfiguration(), new NoAuthCredentialsProvider(), - securityManagerConfiguration + getServerConfigurationController().getSecurityManagerConfiguration() ); } + + @Override public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider, @NotNull DBPWorkspace workspace) throws DBException { @@ -79,6 +87,11 @@ public DBFileController createFileController(@NotNull SMCredentialsProvider cred return new LocalFileController(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath().resolve(DBFileController.DATA_FOLDER)); } + @Override + public CBServerConfigurationControllerEmbedded getServerConfigurationController() { + return serverConfigController; + } + protected void shutdown() { try { if (securityController instanceof CBEmbeddedSecurityController) { @@ -99,4 +112,5 @@ protected void finishSecurityServiceConfiguration( ((CBEmbeddedSecurityController) securityController).finishConfiguration(adminName, adminPassword, authInfoList); } } + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java index 7e35aba999..672c94a040 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java @@ -25,6 +25,7 @@ public class CBConstants { public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf"; public static final String AUTO_CONFIG_FILE_NAME = ".cloudbeaver.auto.conf"; + public static final String PARAM_SERVER_CONFIGURATION = "server"; public static final String PARAM_SERVER_PORT = "serverPort"; public static final String PARAM_SERVER_HOST = "serverHost"; public static final String PARAM_SERVER_NAME = "serverName"; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java index d281748bac..9983f9f844 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java @@ -73,7 +73,7 @@ public class CBPlatform extends BasePlatformImpl { public static final String WORK_DATA_FOLDER_NAME = ".work-data"; @Nullable - private static CBApplication application = null; + private static CBApplication application = null; private Path tempFolder; @@ -189,7 +189,7 @@ public DBPWorkspace getWorkspace() { @NotNull @Override - public CBApplication getApplication() { + public CBApplication getApplication() { return application; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfig.java new file mode 100644 index 0000000000..73f935ada6 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfig.java @@ -0,0 +1,187 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.server; + +import com.google.gson.annotations.SerializedName; +import io.cloudbeaver.auth.CBAuthConstants; +import io.cloudbeaver.model.app.WebServerConfiguration; +import io.cloudbeaver.service.security.db.WebDatabaseConfig; +import org.jkiss.dbeaver.Log; +import org.jkiss.utils.CommonUtils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; + +public class CBServerConfig implements WebServerConfiguration { + + private static final Log log = Log.getLog(CBServerConfig.class); + + protected String serverURL; + protected int serverPort = CBConstants.DEFAULT_SERVER_PORT; + private String serverHost = null; + private String serverName = null; + private String sslConfigurationPath = null; + private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT; + private String rootURI = CBConstants.DEFAULT_ROOT_URI; + private String serviceURI = CBConstants.DEFAULT_SERVICES_URI; + + private String workspaceLocation = CBConstants.DEFAULT_WORKSPACE_LOCATION; + private String driversLocation = CBConstants.DEFAULT_DRIVERS_LOCATION; + @SerializedName("expireSessionAfterPeriod") + private long maxSessionIdleTime = CBAuthConstants.MAX_SESSION_IDLE_TIME; + private boolean develMode = false; + private boolean enableSecurityManager = false; + + @SerializedName("database") + private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig(); + private String staticContent = ""; + + public String getServerURL() { + if (serverURL == null) { + String hostName = serverHost; + if (CommonUtils.isEmpty(hostName)) { + try { + hostName = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + log.debug("Error resolving localhost address: " + e.getMessage()); + hostName = CBApplication.HOST_LOCALHOST; + } + } + serverURL = "http://" + hostName + ":" + serverPort; + } + return serverURL; + } + + public int getServerPort() { + return serverPort; + } + + public String getServerHost() { + return serverHost; + } + + public String getServerName() { + return serverName; + } + + public String getSslConfigurationPath() { + return sslConfigurationPath; + } + + public String getContentRoot() { + return contentRoot; + } + + public String getRootURI() { + return rootURI; + } + + public String getServicesURI() { + return serviceURI; + } + + public String getWorkspaceLocation() { + return workspaceLocation; + } + + public String getDriversLocation() { + return driversLocation; + } + + public WebDatabaseConfig getDatabaseConfiguration() { + return databaseConfiguration; + } + + public String getStaticContent() { + return staticContent; + } + + public void setServerURL(String serverURL) { + this.serverURL = serverURL; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public void setServerHost(String serverHost) { + this.serverHost = serverHost; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public void setSslConfigurationPath(String sslConfigurationPath) { + this.sslConfigurationPath = sslConfigurationPath; + } + + public void setContentRoot(String contentRoot) { + this.contentRoot = contentRoot; + } + + public void setRootURI(String rootURI) { + this.rootURI = rootURI; + } + + public void setServicesURI(String servicesURI) { + this.serviceURI = servicesURI; + } + + public void setWorkspaceLocation(String workspaceLocation) { + this.workspaceLocation = workspaceLocation; + } + + public void setDriversLocation(String driversLocation) { + this.driversLocation = driversLocation; + } + + public void setMaxSessionIdleTime(long maxSessionIdleTime) { + this.maxSessionIdleTime = maxSessionIdleTime; + } + + public void setDevelMode(boolean develMode) { + this.develMode = develMode; + } + + public void setEnableSecurityManager(boolean enableSecurityManager) { + this.enableSecurityManager = enableSecurityManager; + } + + public void setDatabaseConfiguration(WebDatabaseConfig databaseConfiguration) { + this.databaseConfiguration = databaseConfiguration; + } + + public void setStaticContent(String staticContent) { + this.staticContent = staticContent; + } + + @Override + public boolean isDevelMode() { + return develMode; + } + + public long getMaxSessionIdleTime() { + return maxSessionIdleTime; + } + + public boolean isEnableSecurityManager() { + return enableSecurityManager; + } + +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java new file mode 100644 index 0000000000..2565717ce6 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java @@ -0,0 +1,592 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.server; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; +import io.cloudbeaver.model.app.BaseServerConfigurationController; +import io.cloudbeaver.model.app.BaseWebApplication; +import io.cloudbeaver.service.security.PasswordPolicyConfiguration; +import io.cloudbeaver.service.security.SMControllerConfiguration; +import io.cloudbeaver.utils.WebAppUtils; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.ModelPreferences; +import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; +import org.jkiss.dbeaver.model.data.json.JSONUtils; +import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; +import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; +import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings; +import org.jkiss.dbeaver.runtime.DBWorkbench; +import org.jkiss.dbeaver.runtime.IVariableResolver; +import org.jkiss.dbeaver.utils.ContentUtils; +import org.jkiss.dbeaver.utils.PrefUtils; +import org.jkiss.dbeaver.utils.SystemVariablesResolver; +import org.jkiss.utils.CommonUtils; + +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; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class CBServerConfigurationController extends BaseServerConfigurationController { + + private static final Log log = Log.getLog(CBServerConfigurationController.class); + + // Configurations + protected final Map productConfiguration = new HashMap<>(); + protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration(); + private final T serverConfiguration; + private final CBAppConfig appConfiguration = new CBAppConfig(); + private Map externalProperties = new LinkedHashMap<>(); + private Map originalConfigurationProperties = new LinkedHashMap<>(); + + protected CBServerConfigurationController(T serverConfiguration) { + this.serverConfiguration = serverConfiguration; + } + + public String getAuthServiceURL() { + return Stream.of(serverConfiguration.getServerURL(), + serverConfiguration.getRootURI(), + serverConfiguration.getServicesURI()) + .map(WebAppUtils::removeSideSlashes) + .filter(CommonUtils::isNotEmpty) + .collect(Collectors.joining("/")); + } + + @Override + public void loadServerConfiguration(Path configPath) throws DBException { + log.debug("Using configuration [" + configPath + "]"); + + if (!Files.exists(configPath)) { + log.error("Configuration file " + configPath + " doesn't exist. Use defaults."); + } else { + loadConfiguration(configPath); + } + + // Try to load configuration from runtime app config file + Path runtimeConfigPath = getRuntimeAppConfigPath(); + if (Files.exists(runtimeConfigPath)) { + log.debug("Runtime configuration [" + runtimeConfigPath.toAbsolutePath() + "]"); + loadConfiguration(runtimeConfigPath); + } + + // Set default preferences + PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(), + ModelPreferences.UI_DRIVERS_HOME, + getServerConfiguration().getDriversLocation()); + } + + public void loadConfiguration(Path configPath) throws DBException { + CBAppConfig prevConfig = new CBAppConfig(appConfiguration); + Map configProps = readConfiguration(configPath); + try { + parseConfiguration(configProps); + } catch (Exception e) { + throw new DBException("Error parsing server configuration", e); + } + + // Backward compatibility: load configs map + appConfiguration.loadLegacyCustomConfigs(); + + // Merge new config with old one + mergeOldConfiguration(prevConfig); + + patchConfigurationWithProperties(productConfiguration); + } + + protected void parseConfiguration(Map configProps) throws DBException { + Map serverConfig = JSONUtils.getObject(configProps, "server"); + + readExternalProperties(serverConfig); + patchConfigurationWithProperties(configProps); // patch again because properties can be changed + + Gson gson = getGson(); + gson.fromJson( + gson.toJsonTree(serverConfig), + getServerConfiguration().getClass() + ); + + parseServerConfiguration(); + + //SM config + gson.fromJson( + gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)), + SMControllerConfiguration.class + ); + // App config + Map appConfig = JSONUtils.getObject(configProps, "app"); + validateConfiguration(appConfig); + gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class); + + readProductConfiguration(serverConfig, gson); + + } + + public T parseServerConfiguration() { + var config = getServerConfiguration(); + 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 = CBApplication.HOST_LOCALHOST; + } + } + config.setServerURL("http://" + hostName + ":" + config.getServerPort()); + } + var homeDirectory = CBApplication.getInstance().getHomeDirectory().toString(); + config.setContentRoot(WebAppUtils.getRelativePath(config.getContentRoot(), homeDirectory)); + config.setRootURI(readRootUri(config.getRootURI())); + config.setDriversLocation(WebAppUtils.getRelativePath(config.getDriversLocation(), homeDirectory)); + config.setWorkspaceLocation(WebAppUtils.getRelativePath(config.getWorkspaceLocation(), homeDirectory)); + + String staticContentsFile = config.getStaticContent(); + if (!CommonUtils.isEmpty(staticContentsFile)) { + try { + config.setStaticContent(Files.readString(Path.of(staticContentsFile))); + } catch (IOException e) { + log.error("Error reading static contents from " + staticContentsFile, e); + } + } + return config; + } + + protected void validateConfiguration(Map appConfig) throws DBException { + + } + + private void readExternalProperties(Map serverConfig) { + String externalPropertiesFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_EXTERNAL_PROPERTIES); + if (!CommonUtils.isEmpty(externalPropertiesFile)) { + Properties props = new Properties(); + try (InputStream is = Files.newInputStream(Path.of(externalPropertiesFile))) { + props.load(is); + } catch (IOException e) { + log.error("Error loading external properties from " + externalPropertiesFile, e); + } + for (String propName : props.stringPropertyNames()) { + this.externalProperties.put(propName, props.getProperty(propName)); + } + } + } + + protected void mergeOldConfiguration(CBAppConfig prevConfig) { + Map mergedPlugins = Stream.concat( + prevConfig.getPlugins().entrySet().stream(), + appConfiguration.getPlugins().entrySet().stream() + ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (o, o2) -> o2)); + appConfiguration.setPlugins(mergedPlugins); + + Set mergedAuthProviders = Stream.concat( + prevConfig.getAuthCustomConfigurations().stream(), + appConfiguration.getAuthCustomConfigurations().stream() + ) + .collect(Collectors.toCollection(LinkedHashSet::new)); + appConfiguration.setAuthProvidersConfigurations(mergedAuthProviders); + } + + protected void readProductConfiguration(Map serverConfig, Gson gson) + throws DBException { + String productConfigPath = WebAppUtils.getRelativePath( + JSONUtils.getString( + serverConfig, + CBConstants.PARAM_PRODUCT_CONFIGURATION, + CBConstants.DEFAULT_PRODUCT_CONFIGURATION + ), + CBApplication.getInstance().getHomeDirectory().toString() + ); + + if (!CommonUtils.isEmpty(productConfigPath)) { + File productConfigFile = new File(productConfigPath); + if (!productConfigFile.exists()) { + log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'"); + } else { + log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile), + StandardCharsets.UTF_8)) { + productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); + } catch (Exception e) { + throw new DBException("Error reading product configuration", e); + } + } + } + + // Add product config from runtime + File rtConfig = getRuntimeProductConfigFilePath().toFile(); + if (rtConfig.exists()) { + log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) { + productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); + Map flattenConfig = WebAppUtils.flattenMap(this.productConfiguration); + this.productConfiguration.clear(); + this.productConfiguration.putAll(flattenConfig); + } catch (Exception e) { + throw new DBException("Error reading product runtime configuration", e); + } + } + } + + protected Map readConnectionsPermissionsConfiguration(Path parentPath) { + String permissionsConfigPath = WebAppUtils.getRelativePath(CBConstants.DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION, + parentPath); + File permissionsConfigFile = new File(permissionsConfigPath); + if (permissionsConfigFile.exists()) { + log.debug("Load permissions configuration from '" + permissionsConfigFile.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(permissionsConfigFile), + StandardCharsets.UTF_8)) { + return JSONUtils.parseMap(getGson(), reader); + } catch (Exception e) { + log.error("Error reading permissions configuration", e); + } + } + return null; + } + + protected Map readConfiguration(Path configPath) throws DBException { + Map configProps = new LinkedHashMap<>(); + if (Files.exists(configPath)) { + log.debug("Read configuration [" + configPath.toAbsolutePath() + "]"); + // saves original configuration file + this.originalConfigurationProperties.putAll(readConfigurationFile(configPath)); + + configProps.putAll(readConfigurationFile(configPath)); + patchConfigurationWithProperties(configProps); // patch original properties + } + return configProps; + } + + public Map readConfigurationFile(Path path) throws DBException { + try (Reader reader = new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8)) { + return JSONUtils.parseMap(getGson(), reader); + } catch (Exception e) { + throw new DBException("Error parsing server configuration", e); + } + } + + protected GsonBuilder getGsonBuilder() { + // Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431) + InstanceCreator appConfigCreator = type -> appConfiguration; + InstanceCreator navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings(); + InstanceCreator smConfigCreator = type -> securityManagerConfiguration; + InstanceCreator serverConfigCreator = type -> serverConfiguration; + InstanceCreator smPasswordPoliceConfigCreator = + type -> securityManagerConfiguration.getPasswordPolicyConfiguration(); + return new GsonBuilder() + .setLenient() + .registerTypeAdapter(getServerConfiguration().getClass(), serverConfigCreator) + .registerTypeAdapter(CBAppConfig.class, appConfigCreator) + .registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator) + .registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator) + .registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator); + } + + protected void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException { + saveRuntimeConfig( + serverConfiguration.getServerName(), + serverConfiguration.getServerURL(), + serverConfiguration.getMaxSessionIdleTime(), + appConfiguration, + credentialsProvider + ); + } + + protected void saveRuntimeConfig( + String newServerName, + String newServerURL, + long sessionExpireTime, + CBAppConfig appConfig, + SMCredentialsProvider credentialsProvider + ) throws DBException { + if (newServerName == null) { + throw new DBException("Invalid server configuration, server name cannot be empty"); + } + Map configurationProperties = collectConfigurationProperties(newServerName, + newServerURL, + sessionExpireTime, + appConfig); + writeRuntimeConfig(getRuntimeAppConfigPath(), configurationProperties); + } + + private void writeRuntimeConfig(Path runtimeConfigPath, Map configurationProperties) throws DBException { + if (Files.exists(runtimeConfigPath)) { + ContentUtils.makeFileBackup(runtimeConfigPath); + } + + try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigPath.toFile()), StandardCharsets.UTF_8)) { + Gson gson = new GsonBuilder() + .setLenient() + .setPrettyPrinting() + .create(); + gson.toJson(configurationProperties, out); + + } catch (IOException e) { + throw new DBException("Error writing runtime configuration", e); + } + } + + protected Map collectConfigurationProperties( + String newServerName, + String newServerURL, + long sessionExpireTime, + CBAppConfig appConfig + ) { + Map rootConfig = new LinkedHashMap<>(); + { + var originServerConfig = BaseWebApplication.getServerConfigProps(this.originalConfigurationProperties); // get server properties from original configuration file + var serverConfigProperties = collectServerConfigProperties(newServerName, newServerURL, sessionExpireTime, originServerConfig); + rootConfig.put("server", serverConfigProperties); + } + { + var appConfigProperties = new LinkedHashMap(); + Map oldAppConfig = JSONUtils.getObject(this.originalConfigurationProperties, "app"); + rootConfig.put("app", appConfigProperties); + + copyConfigValue( + oldAppConfig, appConfigProperties, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + "supportsCustomConnections", + appConfig.isSupportsCustomConnections()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + "publicCredentialsSaveEnabled", + appConfig.isPublicCredentialsSaveEnabled()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + "adminCredentialsSaveEnabled", + appConfig.isAdminCredentialsSaveEnabled()); + copyConfigValue( + oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth()); + copyConfigValue( + oldAppConfig, appConfigProperties, "forwardProxy", appConfig.isEnabledForwardProxy()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + "linkExternalCredentialsWithUser", + appConfig.isLinkExternalCredentialsWithUser()); + copyConfigValue( + oldAppConfig, appConfigProperties, "redirectOnFederatedAuth", appConfig.isRedirectOnFederatedAuth()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, + appConfig.isResourceManagerEnabled()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + CBConstants.PARAM_SHOW_READ_ONLY_CONN_INFO, + appConfig.isShowReadOnlyConnectionInfo()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + CBConstants.PARAM_CONN_GRANT_ANON_ACCESS, + appConfig.isGrantConnectionsAccessToAnonymousTeam()); + + Map resourceQuotas = new LinkedHashMap<>(); + Map originResourceQuotas = JSONUtils.getObject(oldAppConfig, + CBConstants.PARAM_RESOURCE_QUOTAS); + for (Map.Entry mp : appConfig.getResourceQuotas().entrySet()) { + copyConfigValue(originResourceQuotas, resourceQuotas, mp.getKey(), mp.getValue()); + } + appConfigProperties.put(CBConstants.PARAM_RESOURCE_QUOTAS, resourceQuotas); + + { + // Save only differences in def navigator settings + DBNBrowseSettings navSettings = appConfig.getDefaultNavigatorSettings(); + var navigatorProperties = new LinkedHashMap(); + appConfigProperties.put("defaultNavigatorSettings", navigatorProperties); + + if (navSettings.isShowSystemObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowSystemObjects()) { + navigatorProperties.put("showSystemObjects", navSettings.isShowSystemObjects()); + } + if (navSettings.isShowUtilityObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowUtilityObjects()) { + navigatorProperties.put("showUtilityObjects", navSettings.isShowUtilityObjects()); + } + if (navSettings.isShowOnlyEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowOnlyEntities()) { + navigatorProperties.put("showOnlyEntities", navSettings.isShowOnlyEntities()); + } + if (navSettings.isMergeEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isMergeEntities()) { + navigatorProperties.put("mergeEntities", navSettings.isMergeEntities()); + } + if (navSettings.isHideFolders() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideFolders()) { + navigatorProperties.put("hideFolders", navSettings.isHideFolders()); + } + if (navSettings.isHideSchemas() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideSchemas()) { + navigatorProperties.put("hideSchemas", navSettings.isHideSchemas()); + } + if (navSettings.isHideVirtualModel() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideVirtualModel()) { + navigatorProperties.put("hideVirtualModel", navSettings.isHideVirtualModel()); + } + } + if (appConfig.getEnabledFeatures() != null) { + appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures())); + } + if (appConfig.getEnabledAuthProviders() != null) { + appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders())); + } + if (appConfig.getEnabledDrivers() != null) { + appConfigProperties.put("enabledDrivers", Arrays.asList(appConfig.getEnabledDrivers())); + } + if (appConfig.getDisabledDrivers() != null) { + appConfigProperties.put("disabledDrivers", Arrays.asList(appConfig.getDisabledDrivers())); + } + + if (!CommonUtils.isEmpty(appConfig.getPlugins())) { + appConfigProperties.put("plugins", appConfig.getPlugins()); + } + if (!CommonUtils.isEmpty(appConfig.getAuthCustomConfigurations())) { + appConfigProperties.put("authConfigurations", appConfig.getAuthCustomConfigurations()); + } + } + return rootConfig; + } + + @NotNull + protected Map collectServerConfigProperties( + String newServerName, + String newServerURL, + long sessionExpireTime, + Map originServerConfig + ) { + var serverConfigProperties = new LinkedHashMap(); + if (!CommonUtils.isEmpty(newServerName)) { + copyConfigValue(originServerConfig, + serverConfigProperties, + CBConstants.PARAM_SERVER_NAME, + newServerName); + } + if (!CommonUtils.isEmpty(newServerURL)) { + copyConfigValue( + originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, newServerURL); + } + if (sessionExpireTime > 0) { + copyConfigValue( + originServerConfig, + serverConfigProperties, + CBConstants.PARAM_SESSION_EXPIRE_PERIOD, + sessionExpireTime); + } + return serverConfigProperties; + } + + //////////////////////////////////////////////////////////////////////// + // Configuration utils + + private void patchConfigurationWithProperties(Map configProps) { + IVariableResolver varResolver = new SystemVariablesResolver() { + @Override + public String get(String name) { + String propValue = externalProperties.get(name); + if (propValue != null) { + return propValue; + } + return super.get(name); + } + }; + BaseWebApplication.patchConfigurationWithProperties(configProps, varResolver); + } + + // gets info about patterns from original configuration file and saves it to runtime config + protected void copyConfigValue( + Map oldConfig, + Map newConfig, + String key, + Object defaultValue + ) { + Object value = oldConfig.get(key); + if (value instanceof Map && defaultValue instanceof Map) { + Map subValue = new LinkedHashMap<>(); + Map oldConfigValue = JSONUtils.getObject(oldConfig, key); + for (Map.Entry entry : oldConfigValue.entrySet()) { + copyConfigValue(oldConfigValue, subValue, entry.getKey(), ((Map) defaultValue).get(entry.getKey())); + } + newConfig.put(key, subValue); + } else { + Object newConfigValue = WebAppUtils.getExtractedValue(oldConfig.get(key), defaultValue); + newConfig.put(key, newConfigValue); + } + } + + @NotNull + protected Path getRuntimeAppConfigPath() { + return getDataDirectory(true).resolve(CBConstants.RUNTIME_APP_CONFIG_FILE_NAME); + } + + @NotNull + protected Path getRuntimeProductConfigFilePath() { + return getDataDirectory(false).resolve(CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME); + } + + @NotNull + public Path getDataDirectory(boolean create) { + File dataDir = new File(serverConfiguration.getWorkspaceLocation(), CBConstants.RUNTIME_DATA_DIR_NAME); + if (create && !dataDir.exists()) { + if (!dataDir.mkdirs()) { + log.error("Can't create data directory '" + dataDir.getAbsolutePath() + "'"); + } + } + return dataDir.toPath(); + } + + public void saveProductConfiguration(Map productConfiguration) throws DBException { + Map mergedConfig = WebAppUtils.mergeConfigurations(this.productConfiguration, productConfiguration); + writeRuntimeConfig(getRuntimeProductConfigFilePath(), mergedConfig); + this.productConfiguration.clear(); + this.productConfiguration.putAll(WebAppUtils.flattenMap(mergedConfig)); + } + + public T getServerConfiguration() { + return serverConfiguration; + } + + public CBAppConfig getAppConfiguration() { + return appConfiguration; + } + + public Map getProductConfiguration() { + return productConfiguration; + } + + public SMControllerConfiguration getSecurityManagerConfiguration() { + return securityManagerConfiguration; + } + + private String readRootUri(String uri) { + //slashes are needed to correctly display static resources on ui + if (!uri.endsWith("/")) { + uri = uri + '/'; + } + if (!uri.startsWith("/")) { + uri = '/' + uri; + } + return uri; + } +} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationControllerEmbedded.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationControllerEmbedded.java new file mode 100644 index 0000000000..3ea925ad7c --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationControllerEmbedded.java @@ -0,0 +1,154 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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. + * 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 io.cloudbeaver.server; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; +import io.cloudbeaver.service.security.db.WebDatabaseConfig; +import io.cloudbeaver.utils.WebAppUtils; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.data.json.JSONUtils; +import org.jkiss.utils.CommonUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Server configuration controller for embedded products. + */ +public class CBServerConfigurationControllerEmbedded extends CBServerConfigurationController { + + private static final Log log = Log.getLog(CBServerConfigurationControllerEmbedded.class); + + public CBServerConfigurationControllerEmbedded(T serverConfig) { + super(serverConfig); + } + + @Override + protected void readProductConfiguration(Map serverConfig, Gson gson) + throws DBException { + String productConfigPath = WebAppUtils.getRelativePath( + JSONUtils.getString( + serverConfig, + CBConstants.PARAM_PRODUCT_CONFIGURATION, + CBConstants.DEFAULT_PRODUCT_CONFIGURATION + ), + WebAppUtils.getWebApplication().getHomeDirectory().toString() + ); + + if (!CommonUtils.isEmpty(productConfigPath)) { + File productConfigFile = new File(productConfigPath); + if (!productConfigFile.exists()) { + log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'"); + } else { + log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile), + StandardCharsets.UTF_8)) { + productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); + } catch (Exception e) { + throw new DBException("Error reading product configuration", e); + } + } + } + + // Add product config from runtime + File rtConfig = getRuntimeProductConfigFilePath().toFile(); + if (rtConfig.exists()) { + log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) { + productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); + } catch (Exception e) { + throw new DBException("Error reading product runtime configuration", e); + } + } + } + + @NotNull + @Override + protected Map collectServerConfigProperties( + String newServerName, + String newServerURL, + long sessionExpireTime, + Map originServerConfig + ) { + Map serverConfigProperties = super.collectServerConfigProperties( + newServerName, newServerURL, sessionExpireTime, originServerConfig); + + var databaseConfigProperties = new LinkedHashMap(); + Map oldRuntimeDBConfig = JSONUtils.getObject(originServerConfig, + CBConstants.PARAM_DB_CONFIGURATION); + Gson gson = getGson(); + Map dbConfigMap = gson.fromJson( + gson.toJsonTree(getServerConfiguration().getDatabaseConfiguration()), + JSONUtils.MAP_TYPE_TOKEN + ); + if (!CommonUtils.isEmpty(dbConfigMap)) { + for (Map.Entry mp : dbConfigMap.entrySet()) { + copyConfigValue(oldRuntimeDBConfig, databaseConfigProperties, mp.getKey(), mp.getValue()); + } + serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties); + } + savePasswordPolicyConfig(originServerConfig, serverConfigProperties); + return serverConfigProperties; + } + + + private void savePasswordPolicyConfig(Map originServerConfig, Map serverConfigProperties) { + // save password policy configuration + var passwordPolicyProperties = new LinkedHashMap(); + + var oldRuntimePasswordPolicyConfig = JSONUtils.getObject( + JSONUtils.getObject(originServerConfig, CBConstants.PARAM_SM_CONFIGURATION), + CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION + ); + Gson gson = getGson(); + Map passwordPolicyConfig = gson.fromJson( + gson.toJsonTree(securityManagerConfiguration.getPasswordPolicyConfiguration()), + JSONUtils.MAP_TYPE_TOKEN + ); + if (!CommonUtils.isEmpty(passwordPolicyConfig)) { + for (Map.Entry mp : passwordPolicyConfig.entrySet()) { + copyConfigValue(oldRuntimePasswordPolicyConfig, passwordPolicyProperties, mp.getKey(), mp.getValue()); + } + serverConfigProperties.put( + CBConstants.PARAM_SM_CONFIGURATION, + Map.of(CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION, passwordPolicyProperties) + ); + } + } + + @Override + protected GsonBuilder getGsonBuilder() { + GsonBuilder gsonBuilder = super.getGsonBuilder(); + var databaseConfiguration = getServerConfiguration().getDatabaseConfiguration(); + InstanceCreator dbConfigCreator = type -> databaseConfiguration; + InstanceCreator dbPoolConfigCreator = type -> databaseConfiguration.getPool(); + return gsonBuilder + .registerTypeAdapter(WebDatabaseConfig.class, dbConfigCreator) + .registerTypeAdapter(WebDatabaseConfig.Pool.class, dbPoolConfigCreator); + } + + +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java index ef08514b11..1a4cff4474 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java @@ -16,8 +16,10 @@ */ package io.cloudbeaver.server.jetty; +import io.cloudbeaver.model.app.WebServerConfigurationController; import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.server.CBServerConfig; import io.cloudbeaver.server.graphql.GraphQLEndpoint; import io.cloudbeaver.server.servlets.CBImageServlet; import io.cloudbeaver.server.servlets.CBStaticServlet; @@ -37,6 +39,7 @@ import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.xml.XmlConfiguration; import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.runtime.DBWorkbench; @@ -68,12 +71,12 @@ public CBJettyServer(@NotNull CBApplication application) { } public void runServer() { - CBApplication application = CBApplication.getInstance(); try { + CBServerConfig serverConfiguration = application.getServerConfiguration(); JettyServer server; - int serverPort = application.getServerPort(); - String serverHost = application.getServerHost(); - Path sslPath = application.getSslConfigurationPath(); + int serverPort = serverConfiguration.getServerPort(); + String serverHost = serverConfiguration.getServerHost(); + Path sslPath = getSslConfigurationPath(); boolean sslConfigurationExists = sslPath != null && Files.exists(sslPath); if (sslConfigurationExists) { @@ -97,8 +100,8 @@ public void runServer() { { // Handler configuration ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); - servletContextHandler.setResourceBase(application.getContentRoot()); - String rootURI = application.getRootURI(); + servletContextHandler.setResourceBase(serverConfiguration.getContentRoot()); + String rootURI = serverConfiguration.getRootURI(); servletContextHandler.setContextPath(rootURI); ServletHolder staticServletHolder = new ServletHolder("static", new CBStaticServlet()); @@ -106,11 +109,11 @@ public void runServer() { servletContextHandler.addServlet(staticServletHolder, "/*"); ServletHolder imagesServletHolder = new ServletHolder("images", new CBImageServlet()); - servletContextHandler.addServlet(imagesServletHolder, application.getServicesURI() + "images/*"); + servletContextHandler.addServlet(imagesServletHolder, serverConfiguration.getServicesURI() + "images/*"); servletContextHandler.addServlet(new ServletHolder("status", new CBStatusServlet()), "/status"); - servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), application.getServicesURI() + "gql/*"); + servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), serverConfiguration.getServicesURI() + "gql/*"); servletContextHandler.addEventListener(new CBServerContextListener()); // Add extensions from services @@ -118,7 +121,7 @@ public void runServer() { CBJettyServletContext servletContext = new CBJettyServletContext(servletContextHandler); for (DBWServiceBindingServlet wsd : WebServiceRegistry.getInstance().getWebServices(DBWServiceBindingServlet.class)) { try { - wsd.addServlets(application, servletContext); + wsd.addServlets(this.application, servletContext); } catch (DBException e) { log.error(e.getMessage(), e); } @@ -133,8 +136,8 @@ public void runServer() { wsContainer.setIdleTimeout(Duration.ofMinutes(5)); // Add websockets wsContainer.addMapping( - application.getServicesURI() + "ws/*", - new CBJettyWebSocketManager(application.getSessionManager()) + serverConfiguration.getServicesURI() + "ws/*", + new CBJettyWebSocketManager(this.application.getSessionManager()) ); } ); @@ -172,6 +175,16 @@ public void runServer() { } } + @Nullable + private Path getSslConfigurationPath() { + var sslConfigurationPath = application.getServerConfiguration().getSslConfigurationPath(); + if (sslConfigurationPath == null) { + return null; + } + var sslConfiguration = Path.of(sslConfigurationPath); + return sslConfiguration.isAbsolute() ? sslConfiguration : application.getHomeDirectory().resolve(sslConfiguration); + } + private void initSessionManager( @NotNull CBApplication application, @NotNull ServletContextHandler servletContextHandler diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java index 4bd84edade..ac9d45bd16 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java @@ -29,6 +29,7 @@ import io.cloudbeaver.server.CBAppConfig; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; +import io.cloudbeaver.server.CBServerConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; @@ -212,9 +213,10 @@ private boolean patchIndexHtml(HttpServletResponse response, HttpContent content IOUtils.copyStream(fis, baos); } String indexContents = baos.toString(StandardCharsets.UTF_8); + CBServerConfig serverConfig = CBApplication.getInstance().getServerConfiguration(); indexContents = indexContents - .replace("{ROOT_URI}", CBApplication.getInstance().getRootURI()) - .replace("{STATIC_CONTENT}", CBApplication.getInstance().getStaticContent()); + .replace("{ROOT_URI}", serverConfig.getRootURI()) + .replace("{STATIC_CONTENT}", serverConfig.getStaticContent()); byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8); putHeaders(response, content, indexBytes.length); diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java index bded1f4d3c..6b5992918c 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java @@ -16,18 +16,13 @@ */ package io.cloudbeaver.service.security; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.InstanceCreator; import io.cloudbeaver.model.app.WebAuthApplication; import io.cloudbeaver.service.security.db.CBDatabase; -import io.cloudbeaver.service.security.db.CBDatabaseConfig; +import io.cloudbeaver.service.security.db.WebDatabaseConfig; import io.cloudbeaver.service.security.internal.ClearAuthAttemptInfoJob; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; -import java.util.Map; - /** * Embedded Security Controller Factory */ @@ -43,23 +38,19 @@ public static CBDatabase getDbInstance() { */ public CBEmbeddedSecurityController createSecurityService( T application, - Map databaseConfig, + WebDatabaseConfig databaseConfig, SMCredentialsProvider credentialsProvider, SMControllerConfiguration smConfig ) throws DBException { - boolean initDb = false; if (DB_INSTANCE == null) { synchronized (EmbeddedSecurityControllerFactory.class) { if (DB_INSTANCE == null) { - initDatabase(application, databaseConfig); - initDb = true; + DB_INSTANCE = new CBDatabase(application, databaseConfig); } } - } - var securityController = createEmbeddedSecurityController( - application, DB_INSTANCE, credentialsProvider, smConfig - ); - if (initDb) { + var securityController = createEmbeddedSecurityController( + application, DB_INSTANCE, credentialsProvider, smConfig + ); //FIXME circular dependency DB_INSTANCE.setAdminSecurityController(securityController); DB_INSTANCE.initialize(); @@ -67,21 +58,11 @@ public CBEmbeddedSecurityController createSecurityService( // delete expired auth info job in enterprise products new ClearAuthAttemptInfoJob(securityController).schedule(); } + return securityController; } - return securityController; - } - - private synchronized void initDatabase(WebAuthApplication application, Map databaseConfig) { - CBDatabaseConfig databaseConfiguration = new CBDatabaseConfig(); - InstanceCreator dbConfigCreator = type -> databaseConfiguration; - InstanceCreator dbPoolConfigCreator = type -> databaseConfiguration.getPool(); - Gson gson = new GsonBuilder() - .registerTypeAdapter(CBDatabaseConfig.class, dbConfigCreator) - .registerTypeAdapter(CBDatabaseConfig.Pool.class, dbPoolConfigCreator) - .create(); - gson.fromJson(gson.toJsonTree(databaseConfig), CBDatabaseConfig.class); - - DB_INSTANCE = new CBDatabase(application, databaseConfiguration); + return createEmbeddedSecurityController( + application, DB_INSTANCE, credentialsProvider, smConfig + ); } protected CBEmbeddedSecurityController createEmbeddedSecurityController( diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java index de1706c7ea..4c53365842 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java @@ -82,7 +82,7 @@ public class CBDatabase { private static final String V2_DB_NAME = "cb.h2v2.dat"; private final WebApplication application; - private final CBDatabaseConfig databaseConfiguration; + private final WebDatabaseConfig databaseConfiguration; private PoolingDataSource cbDataSource; private transient volatile Connection exclusiveConnection; @@ -90,7 +90,7 @@ public class CBDatabase { private SMAdminController adminSecurityController; private SQLDialect dialect; - public CBDatabase(WebApplication application, CBDatabaseConfig databaseConfiguration) { + public CBDatabase(WebApplication application, WebDatabaseConfig databaseConfiguration) { this.application = application; this.databaseConfiguration = databaseConfiguration; } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseConfig.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java similarity index 96% rename from server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseConfig.java rename to server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java index a1bd03df78..506cf0042b 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseConfig.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java @@ -22,7 +22,7 @@ /** * Database configuration */ -public class CBDatabaseConfig implements InternalDatabaseConfig { +public class WebDatabaseConfig implements InternalDatabaseConfig { private String driver; private String url; private String user; diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/utils/DBConfigurationUtils.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/utils/DBConfigurationUtils.java index 88f6ed1364..8d1ac5d980 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/utils/DBConfigurationUtils.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/utils/DBConfigurationUtils.java @@ -16,7 +16,7 @@ */ package io.cloudbeaver.service.security.internal.utils; -import io.cloudbeaver.service.security.db.CBDatabaseConfig; +import io.cloudbeaver.service.security.db.WebDatabaseConfig; import org.jkiss.code.Nullable; import org.jkiss.utils.CommonUtils; @@ -37,7 +37,7 @@ public class DBConfigurationUtils { static final String PARAM_DB_POOL_MAX_CONNECTIONS_CONFIGURATION = "maxConnections"; static final String PARAM_DB_POOL_VALIDATION_QUERY_CONFIGURATION = "validationQuery"; - public static Map databaseConfigToMap(@Nullable CBDatabaseConfig databaseConfiguration) { + public static Map databaseConfigToMap(@Nullable WebDatabaseConfig databaseConfiguration) { Map res = new LinkedHashMap<>(); if (databaseConfiguration == null) { return res; @@ -69,12 +69,12 @@ public static Map databaseConfigToMap(@Nullable CBDatabaseConfig return res; } - public static Map poolDatabaseConfigToMap(@Nullable CBDatabaseConfig databaseConfiguration) { + public static Map poolDatabaseConfigToMap(@Nullable WebDatabaseConfig databaseConfiguration) { Map res = new LinkedHashMap<>(); if (databaseConfiguration == null) { return res; } - CBDatabaseConfig.Pool pool = databaseConfiguration.getPool(); + WebDatabaseConfig.Pool pool = databaseConfiguration.getPool(); if (pool == null) { return res; } else {