CB-5375 auto migration to postgres DB for default CB EE configuration (#2809)

* CB-5375 auto migration to postgres DB for default CB EE configuration

* CB-5375 fix test

* CB-5375 move legacy db

* CB-5474 async qm data migration

* CB-5375 comment unused field

---------

Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Alexander Skoblikov
2024-08-08 22:12:50 +03:00
committed by GitHub
co-authored by Evgenia Bezborodova
parent 62393937fc
commit 36c089ed06
14 changed files with 134 additions and 29 deletions
@@ -38,6 +38,7 @@ public class DBWebException extends DBException implements GraphQLError {
public static final String ERROR_CODE_SESSION_EXPIRED = "sessionExpired";
public static final String ERROR_CODE_ACCESS_DENIED = "accessDenied";
public static final String ERROR_CODE_SERVER_NOT_INITIALIZED = "serverNotInitialized";
public static final String ERROR_CODE_LICENSE_DENIED = "licenseRequired";
public static final String ERROR_CODE_IDENT_REQUIRED = "identRequired";
public static final String ERROR_CODE_AUTH_REQUIRED = "authRequired";
@@ -42,6 +42,10 @@ import java.util.Map;
public interface WebApplication extends DBPApplication {
boolean isConfigurationMode();
default boolean isInitializationMode() {
return false;
}
WebAppConfiguration getAppConfiguration();
WebServerConfiguration getServerConfiguration();
@@ -148,6 +148,7 @@ type ServerConfig {
localHostAddress: String
configurationMode: Boolean!
# initializationMode: Boolean! @since(version: "24.1.5")
developmentMode: Boolean!
redirectOnFederatedAuth: Boolean!
distributed: Boolean!
@@ -0,0 +1,27 @@
/*
* 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;
/**
* "Access denied" exception
*/
public class DBWebExceptionServerNotInitialized extends DBWebException {
public DBWebExceptionServerNotInitialized(String message) {
super(message, ERROR_CODE_SERVER_NOT_INITIALIZED);
}
}
@@ -33,4 +33,6 @@ public @interface WebAction {
boolean authRequired() default true;
boolean initializationRequired() default true;
}
@@ -69,6 +69,7 @@ import java.security.Permission;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class controls all aspects of the application's execution
@@ -108,6 +109,8 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
private WebSessionManager sessionManager;
private final Map<String, String> initActions = new ConcurrentHashMap<>();
public CBApplication() {
this.homeDirectory = new File(initHomeFolder());
}
@@ -552,7 +555,8 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
reloadConfiguration(credentialsProvider);
}
public synchronized void reloadConfiguration(@Nullable SMCredentialsProvider credentialsProvider) throws DBException {
public synchronized void reloadConfiguration(@Nullable SMCredentialsProvider credentialsProvider)
throws DBException {
// Re-load runtime configuration
try {
Path runtimeAppConfigPath = getServerConfigurationController().getRuntimeAppConfigPath();
@@ -717,7 +721,10 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
return CBPlatformUI.class;
}
public void saveProductConfiguration(SMCredentialsProvider credentialsProvider, Map<String, Object> productConfiguration) throws DBException {
public void saveProductConfiguration(
SMCredentialsProvider credentialsProvider,
Map<String, Object> productConfiguration
) throws DBException {
getServerConfigurationController().saveProductConfiguration(productConfiguration);
flushConfiguration(credentialsProvider);
sendConfigChangedEvent(credentialsProvider);
@@ -750,4 +757,21 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
public boolean isEnvironmentVariablesAccessible() {
return getAppConfiguration().isSystemVariablesResolvingEnabled();
}
@Override
public boolean isInitializationMode() {
return !initActions.isEmpty();
}
public void addInitAction(@NotNull String actionId, @NotNull String description) {
initActions.put(actionId, description);
}
public void removeInitAction(@NotNull String actionId) {
initActions.remove(actionId);
}
public Map<String, String> getInitActions() {
return Map.copyOf(initActions);
}
}
@@ -330,7 +330,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
.registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator);
}
protected void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException {
public synchronized void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException {
saveRuntimeConfig(
serverConfiguration,
appConfiguration,
@@ -338,7 +338,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
);
}
protected void saveRuntimeConfig(
protected synchronized void saveRuntimeConfig(
@NotNull CBServerConfig serverConfig,
@NotNull CBAppConfig appConfig,
SMCredentialsProvider credentialsProvider
@@ -350,7 +350,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
writeRuntimeConfig(getRuntimeAppConfigPath(), configurationProperties);
}
private void writeRuntimeConfig(Path runtimeConfigPath, Map<String, Object> configurationProperties)
private synchronized void writeRuntimeConfig(Path runtimeConfigPath, Map<String, Object> configurationProperties)
throws DBException {
if (Files.exists(runtimeConfigPath)) {
ContentUtils.makeFileBackup(runtimeConfigPath);
@@ -370,7 +370,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
}
public void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider, @Nullable String newPublicUrl) throws DBException {
public synchronized void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider,
@Nullable String newPublicUrl) throws DBException {
getServerConfiguration().setServerURL(newPublicUrl);
}
@@ -250,6 +250,12 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
private void checkActionPermissions(@NotNull Method method, @NotNull WebAction webAction) throws DBWebException {
CBApplication<?> application = CBApplication.getInstance();
if (application.isInitializationMode() && webAction.initializationRequired()) {
String message = "Server initialization in progress: "
+ String.join(",", application.getInitActions().values()) + ".\nDo not restart the server.";
throw new DBWebExceptionServerNotInitialized(message);
}
String[] reqPermissions = webAction.requirePermissions();
if (reqPermissions.length == 0 && !webAction.authRequired()) {
return;
@@ -258,7 +264,6 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
if (session == null) {
throw new DBWebExceptionAccessDenied("No open session - anonymous access restricted");
}
CBApplication<?> application = CBApplication.getInstance();
if (!application.isConfigurationMode()) {
if (webAction.authRequired() && !session.isAuthorizedInSecurityManager()) {
log.debug("Anonymous access to " + method.getName() + " restricted");
@@ -38,7 +38,7 @@ import java.util.Map;
*/
public interface DBWServiceCore extends DBWService {
@WebAction(authRequired = false)
@WebAction(authRequired = false, initializationRequired = false)
WebServerConfig getServerConfig() throws DBWebException;
@WebAction(authRequired = false)
@@ -13,7 +13,7 @@ Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
org.jkiss.dbeaver.model.sql,
org.jkiss.dbeaver.model.sql.jdbc,
org.jkiss.dbeaver.registry;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
org.jkiss.bundle.apache.dbcp;visibility:=reexport,
io.cloudbeaver.model
Export-Package: io.cloudbeaver.auth.provider.local,
io.cloudbeaver.auth.provider.rp,
@@ -16,10 +16,12 @@
*/
package io.cloudbeaver.service.security;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.service.security.db.CBDatabase;
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
import io.cloudbeaver.service.security.internal.ClearAuthAttemptInfoJob;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
@@ -36,7 +38,7 @@ public class EmbeddedSecurityControllerFactory<T extends WebAuthApplication> {
/**
* Create new security controller instance with custom configuration
*/
public CBEmbeddedSecurityController createSecurityService(
public CBEmbeddedSecurityController<T> createSecurityService(
T application,
WebDatabaseConfig databaseConfig,
SMCredentialsProvider credentialsProvider,
@@ -45,32 +47,53 @@ public class EmbeddedSecurityControllerFactory<T extends WebAuthApplication> {
if (DB_INSTANCE == null) {
synchronized (EmbeddedSecurityControllerFactory.class) {
if (DB_INSTANCE == null) {
DB_INSTANCE = new CBDatabase(application, databaseConfig);
DB_INSTANCE = createAndInitDatabaseInstance(
application,
databaseConfig,
smConfig
);
}
}
var securityController = createEmbeddedSecurityController(
application, DB_INSTANCE, credentialsProvider, smConfig
);
//FIXME circular dependency
DB_INSTANCE.setAdminSecurityController(securityController);
DB_INSTANCE.initialize();
if (application.isLicenseRequired()) {
// delete expired auth info job in enterprise products
new ClearAuthAttemptInfoJob(securityController).schedule();
new ClearAuthAttemptInfoJob(createEmbeddedSecurityController(
application, DB_INSTANCE, new NoAuthCredentialsProvider(), smConfig
)).schedule();
}
return securityController;
}
return createEmbeddedSecurityController(
application, DB_INSTANCE, credentialsProvider, smConfig
);
}
protected CBEmbeddedSecurityController createEmbeddedSecurityController(
protected @NotNull CBDatabase createAndInitDatabaseInstance(
@NotNull T application,
@NotNull WebDatabaseConfig databaseConfig,
@NotNull SMControllerConfiguration smConfig
) throws DBException {
var database = new CBDatabase(application, databaseConfig);
var securityController = createEmbeddedSecurityController(
application, database, new NoAuthCredentialsProvider(), smConfig
);
//FIXME circular dependency
database.setAdminSecurityController(securityController);
try {
database.initialize();
} catch (DBException e) {
database.shutdown();
throw e;
}
return database;
}
protected CBEmbeddedSecurityController<T> createEmbeddedSecurityController(
T application,
CBDatabase database,
SMCredentialsProvider credentialsProvider,
SMControllerConfiguration smConfig
) {
return new CBEmbeddedSecurityController(application, database, credentialsProvider, smConfig);
return new CBEmbeddedSecurityController<T>(application, database, credentialsProvider, smConfig);
}
}
@@ -475,7 +475,7 @@ public class CBDatabase {
// Persistence
private void validateInstancePersistentState(Connection connection) throws IOException, SQLException, DBException {
protected void validateInstancePersistentState(Connection connection) throws IOException, SQLException, DBException {
try (JDBCTransaction txn = new JDBCTransaction(connection)) {
checkInstanceRecord(connection);
var defaultTeamId = application.getAppConfiguration().getDefaultUserTeam();
@@ -587,10 +587,24 @@ public class CBDatabase {
}
public static boolean isDefaultH2Configuration(WebDatabaseConfig databaseConfiguration) {
var v1DefaultUrl = "jdbc:h2:/opt/cloudbeaver/workspace/.data/" + V1_DB_NAME;
var v2DefaultUrl = "jdbc:h2:/opt/cloudbeaver/workspace/.data/" + V2_DB_NAME;
var workspace = WebAppUtils.getWebApplication().getWorkspaceDirectory();
var v1Path = workspace.resolve(".data").resolve(V1_DB_NAME);
var v2Path = workspace.resolve(".data").resolve(V2_DB_NAME);
var v1DefaultUrl = "jdbc:h2:" + v1Path;
var v2DefaultUrl = "jdbc:h2:" + v2Path;
return v1DefaultUrl.equals(databaseConfiguration.getUrl())
|| v2DefaultUrl.equals(databaseConfiguration.getUrl());
}
protected WebDatabaseConfig getDatabaseConfiguration() {
return databaseConfiguration;
}
protected WebApplication getApplication() {
return application;
}
protected SMAdminController getAdminSecurityController() {
return adminSecurityController;
}
}
@@ -63,6 +63,7 @@ public class WebDatabaseConfig implements InternalDatabaseConfig {
return user;
}
@Override
public String getPassword() {
return password;
@@ -85,15 +86,15 @@ public class WebDatabaseConfig implements InternalDatabaseConfig {
return schema;
}
void setPassword(String password) {
public void setPassword(String password) {
this.password = password;
}
void setSchema(String schema) {
public void setSchema(String schema) {
this.schema = schema;
}
void setUser(String user) {
public void setUser(String user) {
this.user = user;
}
}
@@ -162,8 +162,10 @@ public class SQLQueryTranslatorTest {
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), "CREATE SEQUENCE CB_TEST_TYPES_AUTOINC_COLUMN;\n" +
"CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT NOT NULL DEFAULT NEXTVAL('CB_TEST_TYPES_AUTOINC_COLUMN'));" +
"\n");
"CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT NOT NULL DEFAULT NEXTVAL" +
"('CB_TEST_TYPES_AUTOINC_COLUMN'));\n" +
"ALTER SEQUENCE CB_TEST_TYPES_AUTOINC_COLUMN OWNED BY CB_TEST_TYPES.AUTOINC_COLUMN;\n"
);
expectedSqlByDialect.put(new MySQLDialect(), basicSql);
expectedSqlByDialect.put(