From d118ec47a85f9617a3b950c2eaad2cff2ea46099 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Fri, 28 Aug 2020 19:37:32 +0300 Subject: [PATCH] CB-262 Server configuration mode implementation --- .../io/cloudbeaver/server/CBAppConfig.java | 12 +++ .../io/cloudbeaver/server/CBApplication.java | 61 +++++++++++++- .../src/io/cloudbeaver/server/CBDatabase.java | 83 +++++++++++-------- .../service/admin/impl/WebServiceAdmin.java | 16 +++- 4 files changed, 132 insertions(+), 40 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java index 999af270a6..6e27dbadf2 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java @@ -42,10 +42,18 @@ public class CBAppConfig { return authenticationEnabled; } + public void setAuthenticationEnabled(boolean authenticationEnabled) { + this.authenticationEnabled = authenticationEnabled; + } + public boolean isAnonymousAccessEnabled() { return anonymousAccessEnabled; } + public void setAnonymousAccessEnabled(boolean anonymousAccessEnabled) { + this.anonymousAccessEnabled = anonymousAccessEnabled; + } + public String getAnonymousUserRole() { return anonymousUserRole; } @@ -58,6 +66,10 @@ public class CBAppConfig { return supportsCustomConnections; } + public void setSupportsCustomConnections(boolean supportsCustomConnections) { + this.supportsCustomConnections = supportsCustomConnections; + } + public boolean isSupportsConnectionBrowser() { return supportsConnectionBrowser; } 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 b3c452d3c7..6130520040 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 @@ -19,11 +19,13 @@ package io.cloudbeaver.server; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.InstanceCreator; +import com.google.gson.stream.JsonWriter; import io.cloudbeaver.DBWSecurityController; import io.cloudbeaver.server.jetty.CBJettyServer; import org.eclipse.core.runtime.Platform; import org.eclipse.equinox.app.IApplicationContext; import org.eclipse.osgi.service.datalocation.Location; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ModelPreferences; @@ -36,6 +38,7 @@ import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.dbeaver.utils.PrefUtils; import org.jkiss.dbeaver.utils.SystemVariablesResolver; +import org.jkiss.utils.Base64; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.StandardConstants; @@ -160,7 +163,7 @@ public class CBApplication extends BaseApplicationImpl { try { loadConfiguration(configPath); - File runtimeConfigFile = new File(new File(workspaceLocation, CBConstants.RUNTIME_DATA_DIR_NAME), CBConstants.RUNTIME_CONFIG_FILE_NAME); + File runtimeConfigFile = getRuntimeConfigFile(); if (runtimeConfigFile.exists()) { log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]"); parseConfiguration(runtimeConfigFile); @@ -240,6 +243,11 @@ public class CBApplication extends BaseApplicationImpl { return null; } + @NotNull + private File getRuntimeConfigFile() { + return new File(new File(workspaceLocation, CBConstants.RUNTIME_DATA_DIR_NAME), CBConstants.RUNTIME_CONFIG_FILE_NAME); + } + private void initializeDatabase() throws DBException { if (databaseConfiguration == null) { throw new DBException("Database configuration missing"); @@ -379,18 +387,24 @@ public class CBApplication extends BaseApplicationImpl { return configurationMode; } - public synchronized void finishConfiguration() throws DBException { + public synchronized void finishConfiguration( + String newServerName, + String adminName, + String adminPassword, + CBAppConfig appConfig) throws DBException + { if (!isConfigurationMode()) { throw new DBException("Application must be in configuration mode"); } // Save runtime configuration log.debug("Saving runtime configuration"); + saveRuntimeConfig(newServerName, adminPassword, appConfig); // Re-load runtime configuration try { log.debug("Reloading application configuration"); - File runtimeConfigFile = new File(new File(workspaceLocation, CBConstants.RUNTIME_DATA_DIR_NAME), CBConstants.RUNTIME_CONFIG_FILE_NAME); + File runtimeConfigFile = getRuntimeConfigFile(); if (runtimeConfigFile.exists()) { log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]"); parseConfiguration(runtimeConfigFile); @@ -399,11 +413,50 @@ public class CBApplication extends BaseApplicationImpl { throw new DBException("Error parsing configuration", e); } // Re-create database - database.finishConfiguration(); + database.finishConfiguration(adminName, adminPassword); configurationMode = CommonUtils.isEmpty(serverName); } + private void saveRuntimeConfig(String newServerName, String dbPassword, CBAppConfig appConfig) throws DBException { + + File runtimeConfigFile = getRuntimeConfigFile(); + try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) { + Gson gson = new GsonBuilder() + .setLenient() + .create(); + try (JsonWriter json = gson.newJsonWriter(out)) { + json.beginObject(); + { + json.name("server"); + json.beginObject(); + if (!CommonUtils.isEmpty(newServerName)) { + JSONUtils.field(json, "serverName", newServerName); + } + if (!CommonUtils.isEmpty(dbPassword)) { + json.name("database"); + json.beginObject(); + JSONUtils.field(json, "password", Base64.encode(dbPassword.getBytes())); + json.endObject(); + } + json.endObject(); + } + { + json.name("app"); + json.beginObject(); + if (appConfig.isAnonymousAccessEnabled()) JSONUtils.field(json, "anonymousAccessEnabled", true); + if (appConfig.isAuthenticationEnabled()) JSONUtils.field(json, "authenticationEnabled", true); + if (appConfig.isSupportsCustomConnections()) JSONUtils.field(json, "supportsCustomConnections", true); + json.endObject(); + } + json.endObject(); + } + + } catch (IOException e) { + throw new DBException("Error writing runtime configuration", e); + } + } + public DBNBrowseSettings getDefaultNavigatorSettings() { return defaultNavigatorSettings; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBDatabase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBDatabase.java index ae51adcedb..9c1ee0f097 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBDatabase.java @@ -30,6 +30,7 @@ import org.apache.commons.dbcp2.PoolableConnectionFactory; import org.apache.commons.dbcp2.PoolingDataSource; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBConstants; @@ -129,17 +130,21 @@ public class CBDatabase { checkDatabaseStructure(); } - void finishConfiguration() throws DBException { + void finishConfiguration(String adminName, String adminPassword) throws DBException { if (!application.isConfigurationMode()) { throw new DBException("Database is already configured"); } if (cbDataSource != null) { - log.debug("Drop embedded datasource"); + log.debug("Alter admin settings embedded datasource"); try { - // Drop database + // Alter admin password try (Connection connection = cbDataSource.getConnection()) { try (Statement dbStat = connection.createStatement()) { - dbStat.execute("DROP ALL OBJECTS DELETE FILES"); + dbStat.execute("ALTER USER " + databaseConfiguration.getUser() + + " SET PASSWORD '" + databaseConfiguration.getPassword() + "'"); + } + if (!CommonUtils.isEmpty(adminName) && !CommonUtils.isEmpty(adminPassword)) { + createAdminUser(adminName, adminPassword); } } cbDataSource.close(); @@ -272,50 +277,58 @@ public class CBDatabase { Gson gson = new GsonBuilder().setLenient().create(); CBDatabaseInitialData initialData = gson.fromJson(reader, CBDatabaseInitialData.class); - WebUser adminUser = null; - if (!CommonUtils.isEmpty(initialData.getAdminName())) { - // Create admin user - adminUser = new WebUser(initialData.getAdminName()); - serverController.createUser(adminUser); - - String userPassword = initialData.getAdminPassword(); - - // This is how client password will be transmitted from client - String clientPassword = LocalAuthProvider.makeClientPasswordHash(adminUser.getUserId(), userPassword); - - Map credentials = new LinkedHashMap<>(); - credentials.put(LocalAuthProvider.CRED_USER, adminUser.getUserId()); - credentials.put(LocalAuthProvider.CRED_PASSWORD, clientPassword); - - WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(LocalAuthProvider.PROVIDER_ID); - if (authProvider != null) { - serverController.setUserCredentials(adminUser.getUserId(), authProvider, credentials); - } - } - if (!CommonUtils.isEmpty(initialData.getRoles())) { // Create roles for (WebRole role : initialData.getRoles()) { serverController.createRole(role); - if (adminUser != null) { - serverController.setSubjectPermissions(role.getRoleId(), role.getPermissions().toArray(new String[0]), adminUser.getUserId()); - } +// if (adminUser != null) { +// serverController.setSubjectPermissions(role.getRoleId(), role.getPermissions().toArray(new String[0]), adminUser.getUserId()); +// } } } - if (adminUser != null) { - // Grant all roles - WebRole[] allRoles = serverController.readAllRoles(); - serverController.setUserRoles( - adminUser.getUserId(), - Arrays.stream(allRoles).map(WebRole::getRoleId).toArray(String[]::new), - adminUser.getUserId()); + String adminName = initialData.getAdminName(); + String adminPassword = initialData.getAdminPassword(); + + if (!CommonUtils.isEmpty(adminName)) { + // Create admin user + createAdminUser(adminName, adminPassword); } + } catch (Exception e) { log.error("Error loading initial data configuration", e); } } + @NotNull + private WebUser createAdminUser(String adminName, String adminPassword) throws DBCException { + DBWSecurityController serverController = application.getSecurityController(); + WebUser adminUser; + adminUser = new WebUser(adminName); + serverController.createUser(adminUser); + + // This is how client password will be transmitted from client + String clientPassword = LocalAuthProvider.makeClientPasswordHash(adminUser.getUserId(), adminPassword); + + Map credentials = new LinkedHashMap<>(); + credentials.put(LocalAuthProvider.CRED_USER, adminUser.getUserId()); + credentials.put(LocalAuthProvider.CRED_PASSWORD, clientPassword); + + WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(LocalAuthProvider.PROVIDER_ID); + if (authProvider != null) { + serverController.setUserCredentials(adminUser.getUserId(), authProvider, credentials); + } + + // Grant all roles + WebRole[] allRoles = serverController.readAllRoles(); + serverController.setUserRoles( + adminUser.getUserId(), + Arrays.stream(allRoles).map(WebRole::getRoleId).toArray(String[]::new), + adminUser.getUserId()); + + return adminUser; + } + void shutdown() { log.debug("Shutdown database"); if (cbDataSource != null) { diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java index 696191ec44..c9a0c04fd2 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java @@ -29,6 +29,7 @@ import io.cloudbeaver.registry.WebAuthProviderDescriptor; import io.cloudbeaver.registry.WebPermissionDescriptor; import io.cloudbeaver.registry.WebServiceDescriptor; import io.cloudbeaver.registry.WebServiceRegistry; +import io.cloudbeaver.server.CBAppConfig; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.service.admin.*; @@ -297,7 +298,20 @@ public class WebServiceAdmin implements DBWServiceAdmin { @Override public boolean configureServer(WebSession webSession, AdminServerConfig config) throws DBWebException { - throw new DBWebException("Not implemented"); + try { + CBAppConfig appConfig = new CBAppConfig(); + appConfig.setAnonymousAccessEnabled(config.isAnonymousAccessEnabled()); + appConfig.setAuthenticationEnabled(config.isAuthenticationEnabled()); + appConfig.setSupportsCustomConnections(config.isCustomConnectionsEnabled()); + CBApplication.getInstance().finishConfiguration( + config.getServerName(), + config.getAdminName(), + config.getAdminPassword(), + appConfig); + } catch (Throwable e) { + throw new DBWebException("Error configuring server", e); + } + return true; } @Override