Merge branch 'devel' into fix/colors

This commit is contained in:
Naumov Alexey
2021-04-05 17:05:02 +03:00
300 changed files with 4415 additions and 2059 deletions
+8
View File
@@ -22,6 +22,14 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### CloudBeaver 20.0.6 - 2021-03-31
- Value panel for Data Viewer was added: a full text of your cell value on a special panel to view and edit.
- Spatial data support was added: single and multi objects can be shown on a map.
- Possibility to Show or Hide system objects was added to the context menu in the Navigator.
- All product information can be found in public settings.
- A lot of minor UI fixes and improvements.
### CloudBeaver 20.0.5 - 2021-03-18
- New design for the connection form
@@ -34,7 +34,6 @@
},
app: {
anonymousAccessEnabled: true,
authenticationEnabled: true,
anonymousUserRole: "user",
supportsCustomConnections: false,
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.connect.provider.sample;singleton:=true
Bundle-Version: 1.0.24.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.25.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.connect.provider.sample</artifactId>
<version>1.0.24-SNAPSHOT</version>
<version>1.0.25-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 20.0.5.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 20.0.6.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.21.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.22.qualifier
Bundle-Release-Date: 20210405
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.21-SNAPSHOT</version>
<version>1.0.22-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 20.0.5.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 20.0.6.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.WebPlatformActivator
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -88,9 +88,10 @@ type ProductInfo {
type ServerConfig {
name: String!
version: String!
workspaceId: ID!
anonymousAccessEnabled: Boolean
authenticationEnabled: Boolean
authenticationEnabled: Boolean @deprecated
supportsCustomConnections: Boolean
supportsConnectionBrowser: Boolean
supportsWorkspaces: Boolean
@@ -432,7 +433,7 @@ extend type Mutation {
deleteConnection( id: ID! ): Boolean!
createConnectionFromTemplate( templateId: ID! ): ConnectionInfo!
createConnectionFromTemplate( templateId: ID!, connectionName: String ): ConnectionInfo!
# Copies connection configuration from node
copyConnectionFromNode( nodePath: String!, config: ConnectionConfig ): ConnectionInfo!
@@ -17,6 +17,7 @@
package io.cloudbeaver;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.access.DBASession;
@@ -28,10 +29,9 @@ import java.util.Map;
public interface DBWAuthProvider<AUTH_SESSION extends DBASession> {
AUTH_SESSION openSession(
WebSession mainSession,
Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
Map<String, Object> userCredentials, // Saved user credentials (e.g. associated 3rd party provider user name or realm)
Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
@NotNull WebSession mainSession,
@NotNull Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
@NotNull Map<String, Object> userCredentials // Saved user credentials (e.g. associated 3rd party provider user name or realm)
) throws DBException;
void closeSession(AUTH_SESSION session) throws DBException;
@@ -39,22 +39,27 @@ public interface DBWAuthProviderExternal<AUTH_SESSION extends DBASession> extend
* Returns new identifying credentials which can be used to find/create user in database
*/
@NotNull
Map<String, Object> readExternalCredentials(
Map<String, Object> authExternalUser(
@NotNull DBRProgressMonitor monitor,
@NotNull Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
@NotNull Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
) throws DBException;
/**
* Validates that external user may be associated with local user
* @param userCredentials credentials from authExternalUser
* @return new user ID. If activeUser is not null then it must be the same as activeUser ID.
*/
@NotNull
WebUser registerNewUser(
String validateLocalAuth(
@NotNull DBRProgressMonitor monitor,
@NotNull DBWSecurityController securityController,
@NotNull Map<String, Object> providerConfig,
@NotNull Map<String, Object> credentials,
@NotNull Map<String, Object> userCredentials,
@Nullable WebUser activeUser) throws DBException;
@Nullable
String getUserDisplayName(
DBWUserIdentity getUserIdentity(
@NotNull DBRProgressMonitor monitor,
@NotNull Map<String, Object> providerConfig,
@NotNull Map<String, Object> credentials) throws DBException;
@@ -70,6 +70,11 @@ public interface DBWSecurityController {
*/
Map<String, Object> getUserCredentials(String userId, WebAuthProviderDescriptor authProvider) throws DBCException;
/**
* Returns list of auth provider IDs associated with this user
*/
String[] getUserLinkedProviders(String userId) throws DBCException;
///////////////////////////////////////////
// Roles
@@ -0,0 +1,44 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2021 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;
import org.jkiss.code.NotNull;
/**
* User identity provided by auth provider.
*/
public class DBWUserIdentity {
@NotNull
private final String id;
@NotNull
private final String displayName;
public DBWUserIdentity(@NotNull String id, @NotNull String displayName) {
this.id = id;
this.displayName = displayName;
}
@NotNull
public String getId() {
return id;
}
@NotNull
public String getDisplayName() {
return displayName;
}
}
@@ -138,6 +138,18 @@ public class DBWebException extends DBException implements GraphQLError {
}
private static String makeMessage(String message, Throwable cause) {
if (CommonUtils.isEmpty(message)) {
if (cause != null) {
if (cause.getMessage() != null) {
return cause.getMessage();
}
return cause.getClass().getName();
}
return "Unknown internal error";
}
if (CommonUtils.equalObjects(message, cause.getMessage())) {
return message;
}
return message + ":\n" + cause.getMessage();
}
@@ -18,7 +18,11 @@ package io.cloudbeaver.auth.provider.local;
import io.cloudbeaver.DBWAuthProvider;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebAuthProviderPropertyEncryption;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.SecurityUtils;
@@ -35,13 +39,20 @@ public class LocalAuthProvider implements DBWAuthProvider<LocalAuthSession> {
public static final String CRED_PASSWORD = "password";
@Override
public LocalAuthSession openSession(WebSession mainSession, Map<String, Object> providerConfig, Map<String, Object> userCredentials, Map<String, Object> authParameters) throws DBException {
String userName = CommonUtils.toString(authParameters.get(CRED_USER), null);
String storedPasswordHash = CommonUtils.toString(userCredentials.get(CRED_PASSWORD), null);
public LocalAuthSession openSession(@NotNull WebSession mainSession, @NotNull Map<String, Object> providerConfig, @NotNull Map<String, Object> userCredentials) throws DBException {
String userName = CommonUtils.toString(userCredentials.get(CRED_USER), null);
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(PROVIDER_ID);
Map<String, Object> storedCredentials = CBApplication.getInstance().getSecurityController().getUserCredentials(userName, authProvider);
if (storedCredentials == null) {
throw new DBException("Invalid user name or password");
}
String storedPasswordHash = CommonUtils.toString(storedCredentials.get(CRED_PASSWORD), null);
if (CommonUtils.isEmpty(storedPasswordHash)) {
throw new DBException("User has no password (login restricted)");
}
String clientPassword = CommonUtils.toString(authParameters.get(CRED_PASSWORD), null);
String clientPassword = CommonUtils.toString(userCredentials.get(CRED_PASSWORD), null);
if (CommonUtils.isEmpty(clientPassword)) {
throw new DBException("No user password provided");
}
@@ -297,6 +297,11 @@ public class WebConnectionInfo {
this.savedNetworkCredentials = networkCredentials;
}
public void clearSavedCredentials() {
this.savedAuthProperties = null;
this.savedNetworkCredentials = null;
}
@Property
public Map<String, String> getProviderProperties() {
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
@@ -24,7 +24,9 @@ import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.registry.language.PlatformLanguageDescriptor;
import org.jkiss.dbeaver.registry.language.PlatformLanguageRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
@@ -52,6 +54,11 @@ public class WebServerConfig {
return GeneralUtils.getProductVersion().toString();
}
@Property
public String getWorkspaceId() {
return DBWorkbench.getPlatform().getWorkspace().getWorkspaceId();
}
@Property
public boolean isAnonymousAccessEnabled() {
return application.getAppConfiguration().isAnonymousAccessEnabled();
@@ -59,7 +66,8 @@ public class WebServerConfig {
@Property
public boolean isAuthenticationEnabled() {
return application.getAppConfiguration().isAuthenticationEnabled();
String[] enabledAuthProviders = getEnabledAuthProviders();
return enabledAuthProviders == null || !ArrayUtils.isEmpty(enabledAuthProviders);
}
@Property
@@ -18,6 +18,9 @@ package io.cloudbeaver.model;
import io.cloudbeaver.server.CBConstants;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Web server message
*/
@@ -32,13 +35,24 @@ public class WebServerMessage {
}
private final MessageType type;
private long time;
private String message;
private final long time;
private final String message;
private final Throwable error;
public WebServerMessage(MessageType type, String message) {
this(type, message, null);
}
public WebServerMessage(Throwable error) {
this(MessageType.ERROR, error.getMessage(), error);
}
public WebServerMessage(MessageType type, String message, Throwable error) {
this.type = type;
this.time = System.currentTimeMillis();
this.message = message;
this.error = error;
}
public MessageType getType() {
@@ -54,6 +68,11 @@ public class WebServerMessage {
}
public String getStackTrace() {
if (error != null) {
StringWriter buf = new StringWriter();
error.printStackTrace(new PrintWriter(buf, true));
return buf.toString();
}
return null;
}
@@ -17,13 +17,16 @@
package io.cloudbeaver.model.session;
import io.cloudbeaver.DBWAuthProvider;
import io.cloudbeaver.DBWUserIdentity;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.model.user.WebUserOriginInfo;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.access.DBASession;
import java.time.OffsetDateTime;
import java.util.Map;
/**
* WebAuthInfo
@@ -37,49 +40,59 @@ public class WebAuthInfo {
private WebAuthProviderDescriptor authProvider;
private DBASession authSession;
private OffsetDateTime loginTime;
private DBWUserIdentity userIdentity;
private String message;
public WebAuthInfo(WebSession session, WebUser user) {
private transient Map<String, Object> userCredentials;
public WebAuthInfo(
@NotNull WebSession session,
@NotNull WebUser user,
@NotNull WebAuthProviderDescriptor authProvider,
@NotNull DBWUserIdentity userIdentity,
@NotNull DBASession authSession,
@NotNull OffsetDateTime loginTime)
{
this.session = session;
this.user = user;
this.authProvider = authProvider;
this.userIdentity = userIdentity;
this.authSession = authSession;
this.loginTime = loginTime;
}
public WebUser getUser() {
return user;
}
public DBWUserIdentity getUserIdentity() {
return userIdentity;
}
public String getUserId() {
return user.getUserId();
return userIdentity.getId();
}
public String getDisplayName() {
return user.getDisplayName();
return userIdentity.getDisplayName();
}
public WebAuthProviderDescriptor getAuthProvider() {
public String getAuthProvider() {
return authProvider.getId();
}
public WebAuthProviderDescriptor getAuthProviderDescriptor() {
return authProvider;
}
public void setAuthProvider(WebAuthProviderDescriptor authProvider) {
this.authProvider = authProvider;
}
public DBASession getAuthSession() {
return authSession;
}
public void setAuthSession(DBASession authSession) {
this.authSession = authSession;
}
public OffsetDateTime getLoginTime() {
return loginTime;
}
public void setLoginTime(OffsetDateTime loginTime) {
this.loginTime = loginTime;
}
public String getMessage() {
return message;
}
@@ -104,4 +117,13 @@ public class WebAuthInfo {
}
}
}
// Used to keep user credentials during server configuration
public Map<String, Object> getUserCredentials() {
return userCredentials;
}
public void setUserCredentials(Map<String, Object> userCredentials) {
this.userCredentials = userCredentials;
}
}
@@ -92,7 +92,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
private boolean cacheExpired;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private final List<WebServerMessage> progressMessages = new ArrayList<>();
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
private final Map<String, Object> attributes = new HashMap<>();
@@ -101,7 +101,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
private final List<WebAuthInfo> authTokens = new ArrayList<>();
private DBNModel navigatorModel;
private DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
private ProjectMetadata sessionProject;
private final SessionContextImpl sessionAuthContext;
@@ -202,9 +202,11 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
public void forceUserRefresh(WebUser user) {
if (!CommonUtils.equalObjects(this.user, user)) {
// User has changed. We need to reset all session attributes
clearAuthTokens();
try {
resetSessionCache();
} catch (DBCException e) {
addSessionError(e);
log.error(e);
}
}
@@ -273,6 +275,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
try {
this.refreshConnections();
} catch (Exception e) {
addSessionError(e);
log.error("Error getting connection list", e);
}
}
@@ -319,6 +322,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
.getSubjectConnectionAccess(new String[]{subjectId}))
.map(DBWConnectionGrant::getConnectionId).collect(Collectors.toSet());
} catch (DBCException e) {
addSessionError(e);
log.error("Error reading connection grants", e);
return Collections.emptySet();
}
@@ -373,6 +377,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
accessibleConnectionIds = readAccessibleConnectionIds();
} catch (Exception e) {
addSessionError(e);
log.error("Error reading session permissions", e);
}
}
@@ -394,10 +399,10 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
* Returns and clears progress messages
*/
@Association
public List<WebServerMessage> getProgressMessages() {
synchronized (progressMessages) {
List<WebServerMessage> copy = new ArrayList<>(progressMessages);
progressMessages.clear();
public List<WebServerMessage> getSessionMessages() {
synchronized (sessionMessages) {
List<WebServerMessage> copy = new ArrayList<>(sessionMessages);
sessionMessages.clear();
return copy;
}
}
@@ -416,10 +421,13 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
DBWSecurityController.getInstance().createSession(this);
this.persisted = true;
} else {
// Update record
DBWSecurityController.getInstance().updateSession(this);
if (!CBApplication.getInstance().isConfigurationMode()) {
// Update record
DBWSecurityController.getInstance().updateSession(this);
}
}
} catch (Exception e) {
addSessionError(e);
log.error("Error persisting web session", e);
}
}
@@ -544,7 +552,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
return true;
}
public WebAsyncTaskInfo createAndRunAsyncTask(String taskName, WebAsyncTaskProcessor runnable) {
public WebAsyncTaskInfo createAndRunAsyncTask(String taskName, WebAsyncTaskProcessor<?> runnable) {
int taskId = TASK_ID.incrementAndGet();
WebAsyncTaskInfo asyncTask = getAsyncTask(String.valueOf(taskId), taskName, true);
@@ -559,6 +567,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
asyncTask.setStatus("Finished");
asyncTask.setRunning(false);
} catch (InvocationTargetException e) {
addSessionError(e.getTargetException());
asyncTask.setJobError(e.getTargetException());
} catch (InterruptedException e) {
asyncTask.setJobError(e);
@@ -573,19 +582,25 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
return asyncTask;
}
public void addSessionError(Throwable exception) {
synchronized (sessionMessages) {
sessionMessages.add(new WebServerMessage(exception));
}
}
public List<WebServerMessage> readLog(Integer maxEntries, Boolean clearLog) {
synchronized (progressMessages) {
synchronized (sessionMessages) {
List<WebServerMessage> messages = new ArrayList<>();
int entryCount = CommonUtils.toInt(maxEntries);
if (entryCount == 0 || entryCount >= progressMessages.size()) {
messages.addAll(progressMessages);
if (entryCount == 0 || entryCount >= sessionMessages.size()) {
messages.addAll(sessionMessages);
if (CommonUtils.toBoolean(clearLog)) {
progressMessages.clear();
sessionMessages.clear();
}
} else {
messages.addAll(progressMessages.subList(0, maxEntries));
messages.addAll(sessionMessages.subList(0, maxEntries));
if (CommonUtils.toBoolean(clearLog)) {
progressMessages.removeAll(messages);
sessionMessages.removeAll(messages);
}
}
return messages;
@@ -635,7 +650,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
if (providerID != null) {
for (WebAuthInfo ai : authTokens) {
if (ai.getAuthProvider().getId().equals(providerID)) {
if (ai.getAuthProvider().equals(providerID)) {
return ai;
}
}
@@ -660,7 +675,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
}
WebAuthInfo oldAuthInfo = getAuthInfo(authInfo.getAuthProvider().getId());
WebAuthInfo oldAuthInfo = getAuthInfo(authInfo.getAuthProviderDescriptor().getId());
if (oldAuthInfo != null) {
removeAuthInfo(oldAuthInfo);
}
@@ -744,6 +759,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
// credGson.fromJson(credGson.toJsonTree(authProperties), credentials.getClass());
// configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
} catch (DBException e) {
addSessionError(e);
log.error(e);
}
return true;
@@ -770,15 +786,15 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
private class SessionProgressMonitor extends BaseProgressMonitor {
@Override
public void beginTask(String name, int totalWork) {
synchronized (progressMessages) {
progressMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name));
synchronized (sessionMessages) {
sessionMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name));
}
}
@Override
public void subTask(String name) {
synchronized (progressMessages) {
progressMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name));
synchronized (sessionMessages) {
sessionMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name));
}
}
}
@@ -33,7 +33,6 @@ public class CBAppConfig {
public static final DataSourceNavigatorSettings DEFAULT_VIEW_SETTINGS = DataSourceNavigatorSettings.PRESET_FULL.getSettings();
private boolean anonymousAccessEnabled = true;
private boolean authenticationEnabled = true;
private String anonymousUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME;
private String defaultUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME;
private boolean supportsCustomConnections = true;
@@ -48,14 +47,6 @@ public class CBAppConfig {
private DataSourceNavigatorSettings defaultNavigatorSettings = DEFAULT_VIEW_SETTINGS;
private Map<String, Object> plugins = new LinkedHashMap<>();
public boolean isAuthenticationEnabled() {
return authenticationEnabled;
}
public void setAuthenticationEnabled(boolean authenticationEnabled) {
this.authenticationEnabled = authenticationEnabled;
}
public boolean isAnonymousAccessEnabled() {
return anonymousAccessEnabled;
}
@@ -23,6 +23,7 @@ import com.google.gson.stream.JsonWriter;
import io.cloudbeaver.DBWConnectionGrant;
import io.cloudbeaver.DBWSecurityController;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.jetty.CBJettyServer;
import io.cloudbeaver.service.DBWServiceInitializer;
@@ -30,6 +31,7 @@ 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.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ModelPreferences;
@@ -501,18 +503,19 @@ public class CBApplication extends BaseApplicationImpl {
}
public synchronized void finishConfiguration(
String newServerName,
String adminName,
String adminPassword,
@NotNull String newServerName,
@NotNull String adminName,
@Nullable String adminPassword,
@NotNull List<WebAuthInfo> authInfoList,
long sessionExpireTime,
CBAppConfig appConfig) throws DBException
@NotNull CBAppConfig appConfig) throws DBException
{
if (!RECONFIGURATION_ALLOWED && !isConfigurationMode()) {
throw new DBException("Application must be in configuration mode");
}
if (isConfigurationMode()) {
database.finishConfiguration(adminName, adminPassword);
database.finishConfiguration(adminName, adminPassword, authInfoList);
}
// Save runtime configuration
@@ -588,7 +591,6 @@ public class CBApplication extends BaseApplicationImpl {
json.name("app");
json.beginObject();
JSONUtils.field(json, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
JSONUtils.field(json, "authenticationEnabled", appConfig.isAuthenticationEnabled());
JSONUtils.field(json, "supportsCustomConnections", appConfig.isSupportsCustomConnections());
JSONUtils.field(json, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
JSONUtils.field(json, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
@@ -616,6 +618,9 @@ public class CBApplication extends BaseApplicationImpl {
json.endObject();
}
if (appConfig.getEnabledAuthProviders() != null) {
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
}
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins());
@@ -57,6 +57,7 @@ public class CBConstants {
public static final String DEFAULT_DRIVERS_LOCATION = DEFAULT_DEPLOY_LOCATION + "/drivers";
public static final String DEFAULT_WORKSPACE_LOCATION = DEFAULT_DEPLOY_LOCATION + "/workspace";
public static final String DEFAULT_PRODUCT_CONFIGURATION = "conf/product.conf";
public static final String DEFAULT_ADMIN_NAME = "cbadmin";
// Default max idle time (10 minutes)
public static final long MAX_SESSION_IDLE_TIME = 10 * 60 * 1000;
@@ -69,4 +70,5 @@ public class CBConstants {
public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal";
}
@@ -20,6 +20,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.DBWSecurityController;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.user.WebRole;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
@@ -158,7 +159,7 @@ public class CBDatabase {
checkDatabaseStructure();
}
void finishConfiguration(String adminName, String adminPassword) throws DBException {
void finishConfiguration(@NotNull String adminName, @Nullable String adminPassword, @NotNull List<WebAuthInfo> authInfoList) throws DBException {
if (!application.isConfigurationMode()) {
throw new DBException("Database is already configured");
}
@@ -170,10 +171,17 @@ public class CBDatabase {
application.getSecurityController().deleteUser(initialData.getAdminName());
}
// Create new admin user
if (!CommonUtils.isEmpty(adminName) && !CommonUtils.isEmpty(adminPassword)) {
createAdminUser(adminName, adminPassword);
} else {
grantAdminPermissionsToUser(adminName);
createAdminUser(adminName, adminPassword);
// Associate all auth credentials with admin user
for (WebAuthInfo ai : authInfoList) {
if (!ai.getAuthProvider().equals(LocalAuthProvider.PROVIDER_ID)) {
WebAuthProviderDescriptor authProvider = ai.getAuthProviderDescriptor();
Map<String, Object> userCredentials = ai.getUserCredentials();
if (!CommonUtils.isEmpty(userCredentials)) {
application.getSecurityController().setUserCredentials(adminName, authProvider, userCredentials);
}
}
}
}
@@ -326,7 +334,7 @@ public class CBDatabase {
}
@NotNull
private WebUser createAdminUser(String adminName, String adminPassword) throws DBCException {
private WebUser createAdminUser(@NotNull String adminName, @Nullable String adminPassword) throws DBCException {
DBWSecurityController serverController = application.getSecurityController();
WebUser adminUser = serverController.getUserById(adminName);
if (adminUser == null) {
@@ -334,16 +342,18 @@ public class CBDatabase {
serverController.createUser(adminUser);
}
// This is how client password will be transmitted from client
String clientPassword = LocalAuthProvider.makeClientPasswordHash(adminUser.getUserId(), adminPassword);
if (!CommonUtils.isEmpty(adminPassword)) {
// This is how client password will be transmitted from client
String clientPassword = LocalAuthProvider.makeClientPasswordHash(adminUser.getUserId(), adminPassword);
Map<String, Object> credentials = new LinkedHashMap<>();
credentials.put(LocalAuthProvider.CRED_USER, adminUser.getUserId());
credentials.put(LocalAuthProvider.CRED_PASSWORD, clientPassword);
Map<String, Object> 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);
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(LocalAuthProvider.PROVIDER_ID);
if (authProvider != null) {
serverController.setUserCredentials(adminUser.getUserId(), authProvider, credentials);
}
}
grantAdminPermissionsToUser(adminUser.getUserId());
@@ -373,6 +373,29 @@ class CBSecurityController implements DBWSecurityController {
}
}
@Override
public String[] getUserLinkedProviders(String userId) throws DBCException {
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
"SELECT DISTINCT PROVIDER_ID FROM CB_USER_CREDENTIALS\n" +
"WHERE USER_ID=?")) {
dbStat.setString(1, userId);
try (ResultSet dbResult = dbStat.executeQuery()) {
List<String> providerIds = new ArrayList<>();
while (dbResult.next()) {
providerIds.add(dbResult.getString(1));
}
return providerIds.toArray(new String[0]);
}
}
} catch (SQLException e) {
throw new DBCException("Error saving role in database", e);
}
}
///////////////////////////////////////////
// Roles
@@ -28,14 +28,17 @@ import org.jkiss.dbeaver.registry.BaseWorkspaceImpl;
*/
public class CBWorkspace extends BaseWorkspaceImpl {
private final String workspaceId;
public CBWorkspace(DBPPlatform platform, IWorkspace eclipseWorkspace) {
super(platform, eclipseWorkspace);
workspaceId = readWorkspaceId();
}
@NotNull
@Override
public String getWorkspaceId() {
return "web-workspace";
return workspaceId;
}
}
@@ -33,9 +33,11 @@ import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
import io.cloudbeaver.service.WebServiceBindingBase;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.IOUtils;
@@ -256,7 +258,7 @@ public class GraphQLEndpoint extends HttpServlet {
response.getWriter().print(resString);
}
private class WebInstrumentation extends SimpleInstrumentation {
private static class WebInstrumentation extends SimpleInstrumentation {
@Override
public CompletableFuture<ExecutionResult> instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) {
return super.instrumentExecutionResult(executionResult, parameters);
@@ -285,6 +287,12 @@ public class GraphQLEndpoint extends HttpServlet {
}
log.debug("GraphQL call failed at '" + handlerParameters.getPath() + "', " + handlerParameters.getArgumentValues(), exception);
// Log in session
WebSession webSession = WebServiceBindingBase.findWebSession(handlerParameters.getDataFetchingEnvironment());
if (webSession != null) {
webSession.addSessionError(exception);
}
SourceLocation sourceLocation = handlerParameters.getSourceLocation();
ExecutionPath path = handlerParameters.getPath();
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
@@ -45,6 +46,8 @@ import java.util.Set;
*/
public abstract class WebServiceBindingBase<API_TYPE extends DBWService> implements DBWServiceBindingGraphQL {
private static final Log log = Log.getLog(WebServiceBindingBase.class);
private final Class<API_TYPE> apiInterface;
private final API_TYPE serviceImpl;
private final String schemaFileName;
@@ -103,16 +106,16 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
getServletRequest(env), errorOnNotFound);
}
protected static WebSession findWebSession(DataFetchingEnvironment env) {
return CBPlatform.getInstance().getSessionManager().findWebSession(
getServletRequest(env));
}
@NotNull
protected static WebConnectionInfo getWebConnection(DataFetchingEnvironment env) throws DBWebException {
return getWebConnection(getWebSession(env), env.getArgument("connectionId"));
}
public static WebSession findWebSession(DataFetchingEnvironment env) {
return CBPlatform.getInstance().getSessionManager().findWebSession(
getServletRequest(env));
}
@NotNull
public static WebConnectionInfo getWebConnection(WebSession session, String connectionId) throws DBWebException {
return session.getWebConnectionInfo(connectionId);
@@ -132,7 +135,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
try {
WebAction webAction = method.getAnnotation(WebAction.class);
if (webAction != null) {
checkPermissions(webAction);
checkPermissions(method, webAction);
}
return method.invoke(impl, args);
} catch (InvocationTargetException e) {
@@ -140,7 +143,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
}
private void checkPermissions(WebAction webAction) throws DBWebException {
private void checkPermissions(@NotNull Method method, @NotNull WebAction webAction) throws DBWebException {
String[] reqPermissions = webAction.requirePermissions();
if (reqPermissions.length == 0) {
return;
@@ -153,6 +156,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
if (!application.isConfigurationMode()) {
Set<String> sessionPermissions = session.getSessionPermissions();
if (CommonUtils.isEmpty(sessionPermissions)) {
log.debug("Anonymous access to " + method.getName() + " restricted");
throw new DBWebExceptionAccessDenied("Anonymous access restricted");
}
@@ -166,6 +170,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
// Check permissions
for (String rp : reqPermissions) {
if (!sessionPermissions.contains(rp)) {
log.debug("Access to " + method.getName() + " denied for " + session.getUser());
throw new DBWebExceptionAccessDenied("Access denied");
}
}
@@ -115,21 +115,25 @@ public interface DBWServiceCore extends DBWService {
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
@WebAction
boolean deleteConnection(WebSession webSession, String connectionId) throws DBWebException;
boolean deleteConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException;
@WebAction
WebConnectionInfo createConnectionFromTemplate(WebSession webSession, String templateId) throws DBWebException;
WebConnectionInfo createConnectionFromTemplate(
@NotNull WebSession webSession,
@NotNull String templateId,
@Nullable String connectionName) throws DBWebException;
@WebAction()
WebConnectionInfo copyConnectionFromNode(@NotNull WebSession webSession, @NotNull String nodePath, @NotNull WebConnectionConfig config) throws DBWebException;
@WebAction
WebConnectionInfo testConnection(WebSession webSession, WebConnectionConfig connectionConfig) throws DBWebException;
WebConnectionInfo testConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig connectionConfig) throws DBWebException;
@WebAction
WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException;
WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException;
@WebAction
WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException;
WebConnectionInfo closeConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException;
///////////////////////////////////////////
// Navigator settings
@@ -72,7 +72,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
return Collections.emptyList();
}
return getService(env).readSessionLog(
getWebSession(env),
session,
env.getArgument("maxEntries"),
env.getArgument("clearEntries"));
})
@@ -89,7 +89,10 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
.dataFetcher("createConnection", env -> getService(env).createConnection(getWebSession(env), getConnectionConfig(env)))
.dataFetcher("updateConnection", env -> getService(env).updateConnection(getWebSession(env), getConnectionConfig(env)))
.dataFetcher("deleteConnection", env -> getService(env).deleteConnection(getWebSession(env), env.getArgument("id")))
.dataFetcher("createConnectionFromTemplate", env -> getService(env).createConnectionFromTemplate(getWebSession(env), env.getArgument("templateId")))
.dataFetcher("createConnectionFromTemplate", env -> getService(env).createConnectionFromTemplate(
getWebSession(env),
env.getArgument("templateId"),
env.getArgument("connectionName")))
.dataFetcher("copyConnectionFromNode", env -> getService(env).copyConnectionFromNode(
getWebSession(env),
env.getArgument("nodePath"),
@@ -260,8 +260,6 @@ public class WebServiceCore implements DBWServiceCore {
if (dataSourceContainer.isConnected()) {
throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected");
}
//
// WebServiceUtils.initAuthProperties(dataSourceContainer, authProperties);
boolean oldSavePassword = dataSourceContainer.isSavePassword();
try {
@@ -270,7 +268,7 @@ public class WebServiceCore implements DBWServiceCore {
throw new DBWebException("Error connecting to database", e);
} finally {
dataSourceContainer.setSavePassword(oldSavePassword);
connectionInfo.setSavedCredentials(null, null);
connectionInfo.clearSavedCredentials();
}
// Mark all specified network configs as saved
if (networkCredentials != null) {
@@ -288,8 +286,13 @@ public class WebServiceCore implements DBWServiceCore {
}
if (saveCredentials != null && saveCredentials) {
// Save all passed credentials in the datasource container
WebServiceUtils.saveAuthProperties(
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration(),
authProperties,
true);
WebServiceUtils.saveCredentialsInDataSource(connectionInfo, dataSourceContainer, dataSourceContainer.getConnectionConfiguration());
dataSourceContainer.setSavePassword(true);
dataSourceContainer.persistConfiguration();
}
@@ -341,7 +344,7 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public boolean deleteConnection(WebSession webSession, String connectionId) throws DBWebException {
public boolean deleteConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException {
if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
throw new DBWebException("Connection delete is restricted by server configuration");
}
@@ -350,7 +353,11 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebConnectionInfo createConnectionFromTemplate(WebSession webSession, String templateId) throws DBWebException {
public WebConnectionInfo createConnectionFromTemplate(
@NotNull WebSession webSession,
@NotNull String templateId,
@Nullable String connectionName) throws DBWebException
{
DBPDataSourceRegistry templateRegistry = WebServiceUtils.getGlobalDataSourceRegistry();
DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId);
if (dataSourceTemplate == null) {
@@ -361,6 +368,9 @@ public class WebServiceCore implements DBWServiceCore {
DBPDataSourceContainer newDataSource = sessionRegistry.createDataSource(dataSourceTemplate);
((DataSourceDescriptor) newDataSource).setNavigatorSettings(CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings());
if (!CommonUtils.isEmpty(connectionName)) {
newDataSource.setName(connectionName);
}
sessionRegistry.addDataSource(newDataSource);
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource);
@@ -405,7 +415,7 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebConnectionInfo testConnection(WebSession webSession, WebConnectionConfig connectionConfig) throws DBWebException {
public WebConnectionInfo testConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig connectionConfig) throws DBWebException {
String connectionId = connectionConfig.getConnectionId();
connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig
@@ -441,7 +451,7 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException {
public WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException {
DBRProgressMonitor monitor = webSession.getProgressMonitor();
monitor.beginTask("Instantiate SSH tunnel", 2);
@@ -494,7 +504,7 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException {
public WebConnectionInfo closeConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException {
return closeAndDeleteConnection(webSession, connectionId, false);
}
@@ -519,7 +529,7 @@ public class WebServiceCore implements DBWServiceCore {
webSession.removeConnection(connectionInfo);
} else {
// Just reset saved credentials
connectionInfo.setSavedCredentials(null, null);
connectionInfo.clearSavedCredentials();
}
return connectionInfo;
@@ -25,6 +25,7 @@ import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionContext;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionRequest;
import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager;
import java.util.Map;
@@ -58,6 +59,11 @@ public class WebSQLCompletionContext implements SQLCompletionContext {
return sqlContext.getProcessor().getSyntaxManager();
}
@Override
public SQLRuleManager getRuleManager() {
return sqlContext.getProcessor().getRuleManager();
}
@Override
public boolean isUseFQNames() {
return false;
@@ -19,6 +19,7 @@ package io.cloudbeaver.service.sql;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.utils.Pair;
/**
@@ -77,7 +78,7 @@ public class WebSQLDialectInfo {
}
public String getScriptDelimiter() {
return dialect.getScriptDelimiter();
return SQLUtils.getDefaultScriptDelimiter(dialect);
}
}
@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.utils.CommonUtils;
@@ -56,6 +57,7 @@ public class WebSQLProcessor {
private final WebSession webSession;
private final WebConnectionInfo connection;
private final SQLSyntaxManager syntaxManager;
private final SQLRuleManager ruleManager;
private final Map<String, WebSQLContextInfo> contexts = new LinkedHashMap<>();
private AtomicInteger contextId = new AtomicInteger();
@@ -66,6 +68,9 @@ public class WebSQLProcessor {
syntaxManager = new SQLSyntaxManager();
syntaxManager.init(connection.getDataSource());
ruleManager = new SQLRuleManager(syntaxManager);
ruleManager.loadRules(connection.getDataSource(), false);
}
void dispose() {
@@ -87,6 +92,10 @@ public class WebSQLProcessor {
return syntaxManager;
}
SQLRuleManager getRuleManager() {
return ruleManager;
}
DBCExecutionContext getExecutionContext() {
return DBUtils.getDefaultContext(connection.getDataSource(), false);
}
@@ -23,6 +23,8 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.gis.DBGeometry;
import org.jkiss.dbeaver.model.gis.GisConstants;
import org.jkiss.dbeaver.model.gis.GisTransformUtils;
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.jkiss.dbeaver.utils.ContentUtils;
@@ -143,6 +145,11 @@ public class WebSQLUtils {
map.put("srid", value.getSRID());
map.put("text", value.toString());
map.put("properties", value.getProperties());
DBGeometry xValue = GisTransformUtils.transformToSRID(value, GisConstants.SRID_4326);
if (xValue != null && xValue != value) {
map.put("mapText", xValue.toString());
}
return map;
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.20.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.21.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.20-SNAPSHOT</version>
<version>1.0.21-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -57,7 +57,7 @@ input ServerConfigInput {
publicCredentialsSaveEnabled: Boolean
adminCredentialsSaveEnabled: Boolean
enabledAuthProviders: [ID!]!
enabledAuthProviders: [ID!]
sessionExpireTime: Int
}
@@ -35,7 +35,6 @@ public class AdminServerConfig {
private String adminPassword;
private boolean anonymousAccessEnabled;
private boolean authenticationEnabled;
private boolean customConnectionsEnabled;
private boolean publicCredentialsSaveEnabled;
private boolean adminCredentialsSaveEnabled;
@@ -50,7 +49,6 @@ public class AdminServerConfig {
CBAppConfig appConfig = CBApplication.getInstance().getAppConfiguration();
this.anonymousAccessEnabled = JSONUtils.getBoolean(params, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
this.authenticationEnabled = JSONUtils.getBoolean(params, "authenticationEnabled", appConfig.isAuthenticationEnabled());
this.customConnectionsEnabled = JSONUtils.getBoolean(params, "customConnectionsEnabled", appConfig.isSupportsCustomConnections());
this.publicCredentialsSaveEnabled = JSONUtils.getBoolean(params, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
this.adminCredentialsSaveEnabled = JSONUtils.getBoolean(params, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
@@ -91,10 +89,6 @@ public class AdminServerConfig {
return anonymousAccessEnabled;
}
public boolean isAuthenticationEnabled() {
return authenticationEnabled;
}
public boolean isCustomConnectionsEnabled() {
return customConnectionsEnabled;
}
@@ -22,6 +22,7 @@ import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebRole;
import io.cloudbeaver.model.user.WebUser;
@@ -31,6 +32,7 @@ import io.cloudbeaver.registry.WebServiceDescriptor;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBAppConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.admin.*;
@@ -352,7 +354,6 @@ public class WebServiceAdmin implements DBWServiceAdmin {
try {
CBAppConfig appConfig = new CBAppConfig();
appConfig.setAnonymousAccessEnabled(config.isAnonymousAccessEnabled());
appConfig.setAuthenticationEnabled(config.isAuthenticationEnabled());
appConfig.setSupportsCustomConnections(config.isCustomConnectionsEnabled());
appConfig.setPublicCredentialsSaveEnabled(config.isPublicCredentialsSaveEnabled());
appConfig.setAdminCredentialsSaveEnabled(config.isAdminCredentialsSaveEnabled());
@@ -366,6 +367,8 @@ public class WebServiceAdmin implements DBWServiceAdmin {
appConfig.setDefaultNavigatorSettings(
CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings());
List<WebAuthInfo> authInfoList = webSession.getAllAuthInfo();
String adminName = config.getAdminName();
String adminPassword = config.getAdminPassword();
if (CommonUtils.isEmpty(adminName)) {
@@ -374,6 +377,15 @@ public class WebServiceAdmin implements DBWServiceAdmin {
adminName = curUser == null ? null : curUser.getUserId();
adminPassword = null;
}
if (CommonUtils.isEmpty(adminName)) {
// Try to get admin name from existing authentications (first one)
if (!authInfoList.isEmpty()) {
adminName = authInfoList.get(0).getUserId();
}
}
if (CommonUtils.isEmpty(adminName)) {
adminName = CBConstants.DEFAULT_ADMIN_NAME;
}
// Patch configuration by services
for (DBWServiceServerConfigurator wsc : WebServiceRegistry.getInstance().getWebServices(DBWServiceServerConfigurator.class)) {
@@ -388,6 +400,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
config.getServerName(),
adminName,
adminPassword,
authInfoList,
config.getSessionExpireTime(),
appConfig);
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.20.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.21.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.20-SNAPSHOT</version>
<version>1.0.21-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -37,6 +37,12 @@ type UserAuthToken {
# Authorization time
loginTime: DateTime!
# User identity (aka user name) specific to auth provider
userId: String!
# User display name specific to auth provider
displayName: String!
# Optional login message
message: String
@@ -44,43 +50,29 @@ type UserAuthToken {
origin: ObjectOrigin!
}
type UserAuthInfo {
# User unique identifier
userId: String! @deprecated
# Human readable display name. May be null
displayName: String @deprecated
# Auth provider ID
authProvider: String! @deprecated
loginTime: DateTime! @deprecated
# Optional login message
message: String @deprecated
origin: ObjectOrigin! @deprecated
}
type UserInfo {
# User unique identifier
userId: String!
# Human readable display name. May be null
# Human readable display name. It is taken from the first auth provider which was used for user login.
displayName: String
authTokens: [UserAuthToken!]!
linkedAuthProviders: [String!]!
}
extend type Query {
# Authorize user using specified auth provider
# Authorize user using specified auth provider. If linkUser=true then associates new
authLogin(provider: ID!, credentials: Object!, linkUser: Boolean): UserAuthToken!
authLogin(provider: ID!, credentials: Object!): UserAuthInfo!
# Logouts user. If provider not specified then all authorizations are revoked from session.
authLogout(provider: ID): Boolean
# Active user information. null is no user was authorized within session
activeUser: UserInfo
sessionUser: UserAuthInfo @deprecated
authProviders: [AuthProviderInfo!]!
}
@@ -33,7 +33,11 @@ import java.util.Map;
public interface DBWServiceAuth extends DBWService {
@WebAction(requirePermissions = {} )
WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map<String, Object> credentials) throws DBWebException;
WebAuthInfo authLogin(
@NotNull WebSession webSession,
@NotNull String providerId,
@NotNull Map<String, Object> credentials,
boolean linkWithActiveUser) throws DBWebException;
@WebAction(requirePermissions = {} )
void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException;
@@ -41,11 +45,7 @@ public interface DBWServiceAuth extends DBWService {
@WebAction(requirePermissions = {})
WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException;
@Deprecated
@WebAction(requirePermissions = {})
WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException;
@WebAction(requirePermissions = {})
WebAuthProviderInfo[] getAuthProviders(@NotNull WebSession webSession);
WebAuthProviderInfo[] getAuthProviders();
}
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.auth.impl.WebServiceAuthImpl;
import org.jkiss.utils.CommonUtils;
/**
* Web service implementation
@@ -38,14 +39,14 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
.dataFetcher("authLogin", env -> getService(env).authLogin(
getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("credentials")))
env.getArgument("credentials"),
CommonUtils.toBoolean(env.getArgument("linkUser"))))
.dataFetcher("authLogout", env -> {
getService(env).authLogout(getWebSession(env), env.getArgument("provider"));
return true;
})
.dataFetcher("activeUser", env -> getService(env).activeUser(getWebSession(env, false)))
.dataFetcher("sessionUser", env -> getService(env).sessionUser(getWebSession(env, false)))
.dataFetcher("authProviders", env -> getService(env).getAuthProviders(getWebSession(env)))
.dataFetcher("authProviders", env -> getService(env).getAuthProviders())
;
}
@@ -0,0 +1,62 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2021 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.service.auth;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.model.user.WebUserOriginInfo;
import org.jkiss.code.NotNull;
import java.time.OffsetDateTime;
/**
* WebUserInfo
*/
public class WebUserAuthToken {
private final WebSession session;
private final WebUser user;
private final WebAuthInfo authInfo;
WebUserAuthToken(@NotNull WebSession session, @NotNull WebUser user, @NotNull WebAuthInfo authInfo) {
this.session = session;
this.user = user;
this.authInfo = authInfo;
}
@NotNull
public String getAuthProvider() {
return authInfo.getAuthProviderDescriptor().getId();
}
@NotNull
public OffsetDateTime getLoginTime() {
return authInfo.getLoginTime();
}
@NotNull
public String getMessage() {
return authInfo.getMessage();
}
@NotNull
public WebUserOriginInfo getOrigin() {
return new WebUserOriginInfo(session, user, authInfo.getAuthProviderDescriptor());
}
}
@@ -16,12 +16,17 @@
*/
package io.cloudbeaver.service.auth;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.server.CBApplication;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.meta.Property;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* WebUserInfo
@@ -32,22 +37,41 @@ public class WebUserInfo {
private final WebSession session;
private final WebUser user;
private String[] linkedProviders;
public WebUserInfo(WebSession session, WebUser user) {
this.session = session;
this.user = user;
}
@Property
public String getUserId() {
return user.getUserId();
}
@Property
public String getDisplayName() {
return user.getDisplayName();
}
public List<WebAuthInfo> getAuthTokens() {
return session.getAllAuthInfo();
@Property
public List<WebUserAuthToken> getAuthTokens() {
return session.getAllAuthInfo().stream()
.map(ai -> new WebUserAuthToken(session, user, ai))
.collect(Collectors.toList());
}
@Property
public List<String> getLinkedAuthProviders() throws DBWebException {
if (linkedProviders == null) {
try {
linkedProviders = CBApplication.getInstance().getSecurityController()
.getUserLinkedProviders(session.getUser().getUserId());
} catch (DBCException e) {
throw new DBWebException("Error reading user linked providers");
}
}
return Arrays.asList(linkedProviders);
}
}
@@ -33,6 +33,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.access.DBASession;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.time.OffsetDateTime;
@@ -47,12 +48,35 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
private static final Log log = Log.getLog(WebServiceAuthImpl.class);
@Override
public WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map<String, Object> authParameters) throws DBWebException {
DBWSecurityController serverController = CBPlatform.getInstance().getApplication().getSecurityController();
public WebAuthInfo authLogin(
@NotNull WebSession webSession,
@NotNull String providerId,
@NotNull Map<String, Object> authParameters,
boolean linkWithActiveUser) throws DBWebException {
DBWSecurityController securityController = CBPlatform.getInstance().getApplication().getSecurityController();
if (CommonUtils.isEmpty(providerId)) {
throw new DBWebException("Missing auth provider parameter");
}
boolean configMode = CBApplication.getInstance().isConfigurationMode();
// Check enabled auth providers
boolean providerEnabled = true;
String[] enabledAuthProviders = CBApplication.getInstance().getAppConfiguration().getEnabledAuthProviders();
if (enabledAuthProviders != null && !ArrayUtils.contains(enabledAuthProviders, providerId)) {
providerEnabled = false;
}
if (configMode || webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
// 1. Admin can authorize in any providers
// 2. When it authorizes in non-local provider for the first time we force linkUser flag
if (!providerEnabled && webSession.getUser() != null) {
linkWithActiveUser = true;
}
} else {
if (!providerEnabled) {
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
}
}
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(providerId);
if (authProvider == null) {
throw new DBWebException("Invalid auth provider '" + providerId + "'");
@@ -63,55 +87,116 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
DBWAuthProvider<?> authProviderInstance = authProvider.getInstance();
DBWAuthProviderExternal<?> authProviderExternal = authProviderInstance instanceof DBWAuthProviderExternal<?> ?
(DBWAuthProviderExternal<?>) authProviderInstance : null;
Map<String, Object> userCredentials;
if (authProviderExternal != null) {
authParameters = authProviderExternal.readExternalCredentials(webSession.getProgressMonitor(), providerConfig, authParameters);
userCredentials = authProviderExternal.authExternalUser(webSession.getProgressMonitor(), providerConfig, authParameters);
} else {
// User credentials are the same as auth parameters
userCredentials = authParameters;
}
WebUser user = webSession.getUser();
String userId = serverController.getUserByCredentials(authProvider, authParameters);
if (userId == null) {
// User doesn't exist. We can create new user automatically if auth provider supports this
WebUser user = null;
String userId;
DBASession authSession;
if (configMode) {
if (authProviderExternal != null) {
user = authProviderExternal.registerNewUser(webSession.getProgressMonitor(), serverController, providerConfig, authParameters, user);
userId = user.getUserId();
userId = authProviderExternal.validateLocalAuth(
webSession.getProgressMonitor(),
securityController,
providerConfig,
userCredentials,
webSession.getUser());
} else {
userId = "temp_config_admin";
}
} else {
WebUser curUser = webSession.getUser();
userId = securityController.getUserByCredentials(authProvider, userCredentials);
if (userId == null) {
throw new DBCException("Invalid user credentials");
// User doesn't exist. We can create new user automatically if auth provider supports this
if (authProviderExternal != null) {
userId = authProviderExternal.validateLocalAuth(
webSession.getProgressMonitor(),
securityController,
providerConfig,
userCredentials,
curUser);
if (curUser == null || !userId.equals(curUser.getUserId())) {
// Create new user
curUser = securityController.getUserById(userId);
if (curUser == null) {
curUser = new WebUser(userId);
securityController.createUser(curUser);
String defaultRoleName = CBPlatform.getInstance().getApplication().getAppConfiguration().getDefaultUserRole();
if (!CommonUtils.isEmpty(defaultRoleName)) {
securityController.setUserRoles(
userId,
new String[]{defaultRoleName},
userId);
}
}
} else {
// We may need to associate new credentials with active user
if (linkWithActiveUser) {
securityController.setUserCredentials(userId, authProvider, userCredentials);
}
}
}
if (userId == null) {
throw new DBCException("Invalid user credentials");
}
}
}
if (user != null && !user.getUserId().equals(userId)) {
log.debug("Attempt to authorize user '" + userId + "' while user '" + user.getUserId() + "' already authorized");
throw new DBCException("You cannot authorize with different users credentials");
}
// Check for auth enabled. Auth is always enabled for admins
if (!CBApplication.getInstance().getAppConfiguration().isAuthenticationEnabled()) {
if (!serverController.getUserPermissions(userId).contains(DBWConstants.PERMISSION_ADMIN)) {
throw new DBWebException("Authentication was disabled for this server");
if (curUser != null && !curUser.getUserId().equals(userId)) {
log.debug("Attempt to authorize user '" + userId + "' while user '" + curUser.getUserId() + "' already authorized");
throw new DBCException("You cannot authorize with different users credentials");
}
user = curUser;
}
Map<String, Object> userCredentials = serverController.getUserCredentials(userId, authProvider);
DBASession authSession = authProviderInstance.openSession(
webSession,
providerConfig,
userCredentials,
authParameters);
if (user == null) {
user = new WebUser(userId);
}
DBWUserIdentity userIdentity = null;
if (authProviderExternal != null) {
user.setDisplayName(authProviderExternal.getUserDisplayName(webSession.getProgressMonitor(), providerConfig, authParameters));
try {
userIdentity =
authProviderExternal.getUserIdentity(
webSession.getProgressMonitor(),
providerConfig,
userCredentials);
} catch (DBException e) {
log.debug("Error reading auth display name from provider " + providerId, e);
}
}
if (userIdentity == null) {
userIdentity = new DBWUserIdentity(userId, userId);
}
if (CommonUtils.isEmpty(user.getDisplayName())) {
user.setDisplayName(userIdentity.getDisplayName());
}
WebAuthInfo authInfo = new WebAuthInfo(webSession, user);
authInfo.setLoginTime(OffsetDateTime.now());
authInfo.setAuthProvider(authProvider);
authInfo.setAuthSession(authSession);
authSession = authProviderInstance.openSession(
webSession,
providerConfig,
userCredentials
);
WebAuthInfo authInfo = new WebAuthInfo(
webSession,
user,
authProvider,
userIdentity,
authSession,
OffsetDateTime.now());
authInfo.setMessage("Authenticated with " + authProvider.getLabel() + " provider");
if (configMode) {
authInfo.setUserCredentials(userCredentials);
}
webSession.addAuthInfo(authInfo);
return authInfo;
@@ -137,15 +222,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
@Override
public WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException {
if (webSession.getUser() == null) {
return null;
}
return webSession.getAuthInfo(null);
}
@Override
public WebAuthProviderInfo[] getAuthProviders(@NotNull WebSession webSession) {
public WebAuthProviderInfo[] getAuthProviders() {
return WebServiceRegistry.getInstance().getAuthProviders()
.stream().map(WebAuthProviderInfo::new)
.toArray(WebAuthProviderInfo[]::new);
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.21.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.22.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.21-SNAPSHOT</version>
<version>1.0.22-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.24.qualifier
Bundle-Release-Date: 20210322
Bundle-Version: 1.0.25.qualifier
Bundle-Release-Date: 20210405
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.24-SNAPSHOT</version>
<version>1.0.25-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.44.qualifier"
version="1.0.45.qualifier"
provider-name="DBeaver Corp">
<plugin id="io.cloudbeaver.resources.drivers.base" download-size="0" install-size="0" version="0.0.0"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
<version>1.0.44-SNAPSHOT</version>
<version>1.0.45-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="20.0.5.qualifier"
version="20.0.6.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.sample.feature"
label="%featureName"
version="1.0.24.qualifier"
version="1.0.25.qualifier"
provider-name="%providerName">
<description>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.sample.feature</artifactId>
<version>1.0.24-SNAPSHOT</version>
<version>1.0.25-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="20.0.5.qualifier"
version="20.0.6.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<dbeaver-product>CloudBeaver CE</dbeaver-product>
<dbeaver-version>20.0.5</dbeaver-version>
<dbeaver-version>20.0.6</dbeaver-version>
</properties>
<modules>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?pde version="3.5"?>
<product name="Cloudbeaver Provided Sample" uid="io.cloudbeaver.provided.sample.product" id="io.cloudbeaver.provided.sample.product" application="io.cloudbeaver.server.standalone" version="20.0.5.qualifier" useFeatures="true" includeLaunchers="false">
<product name="Cloudbeaver Provided Sample" uid="io.cloudbeaver.provided.sample.product" id="io.cloudbeaver.provided.sample.product" application="io.cloudbeaver.server.standalone" version="20.0.6.qualifier" useFeatures="true" includeLaunchers="false">
<configIni use="default">
</configIni>
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<artifactId>provided-sample</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Provided Sample Product</name>
@@ -2,7 +2,7 @@
<?pde version="3.5"?>
<product name="CloudBeaver ce Server" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
application="io.cloudbeaver.product.ce.application" version="20.0.5.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="20.0.6.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
@@ -51,10 +51,12 @@
<setEntry value="org.apache.felix.scr@1:true"/>
<setEntry value="org.bouncycastle.bcpkix@default:default"/>
<setEntry value="org.bouncycastle.bcprov@default:default"/>
<setEntry value="org.eclipse.core.commands@default:default"/>
<setEntry value="org.eclipse.core.contenttype@default:default"/>
<setEntry value="org.eclipse.core.databinding.observable@default:default"/>
<setEntry value="org.eclipse.core.databinding.property@default:default"/>
<setEntry value="org.eclipse.core.databinding@default:default"/>
<setEntry value="org.eclipse.core.expressions@default:default"/>
<setEntry value="org.eclipse.core.filebuffers@default:default"/>
<setEntry value="org.eclipse.core.filesystem.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.core.filesystem@default:default"/>
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>20.0.5-SNAPSHOT</version>
<version>20.0.6-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
+1 -1
View File
@@ -85,7 +85,7 @@ module.exports = {
ignoreTrailingComments: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignorePattern: "^export\\s(const\\s\\w+:?.+=.+function\\s|function\\s)\\w+\\s*\\(\\s*\\{$"
ignorePattern: "^export\\s(const\\s\\w+:?.+=.+function\\s|function\s)\\w+\\s*\\(.*\\{$"
}
],
'import-helpers/order-imports': [
@@ -79,7 +79,7 @@ export const Administration: React.FC<Props> = observer(function Administration(
const contentRef = useRef<HTMLDivElement>(null);
const administrationItemService = useService(AdministrationItemService);
const items = administrationItemService.getActiveItems(configurationWizard);
const hasOnlyActive = items.some(filterOnlyActive);
const hasOnlyActive = items.some(filterOnlyActive(configurationWizard));
useLayoutEffect(() => {
contentRef.current?.scrollTo({ top: 0, left: 0 });
@@ -6,12 +6,14 @@
* you may not use this file except in compliance with the License.
*/
import styled from 'reshadow';
interface Props {
className?: string;
}
export const AdministrationTools: React.FC<Props> = function AdministrationTools({ children, className }) {
return (
return styled()(
<administration-tools as='div' className={className}>
{children}
</administration-tools>
@@ -24,7 +24,7 @@ export const DrawerItem = observer(function DrawerItem({
disabled = !configurationWizardService.isStepAvailable(item.name);
}
const onlyActive = filterOnlyActive(item) ? false : disabled;
const onlyActive = filterOnlyActive(configurationWizard)(item) ? false : disabled;
return (
<Component
@@ -8,6 +8,7 @@
import { observer } from 'mobx-react-lite';
import { Loader } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
@@ -34,6 +35,10 @@ export const ItemContent: React.FC<Props> = observer(function ItemContent({
return null;
}
if (administrationItemService.itemActivating) {
return <Loader />;
}
if (activeScreen.sub) {
const sub = administrationItemService.getItemSub(item, activeScreen.sub);
if (sub) {
@@ -9,6 +9,7 @@
import { observable, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor, IExecutorHandler } from '@cloudbeaver/core-executor';
import type { RouterState } from '@cloudbeaver/core-routing';
import { filterConfigurationWizard } from './filterConfigurationWizard';
@@ -18,19 +19,76 @@ import {
import type { IAdministrationItemRoute } from './IAdministrationItemRoute';
import { orderAdministrationItems } from './orderAdministrationItems';
interface IActivationData {
screen: IAdministrationItemRoute;
configurationWizard: boolean;
outside: boolean;
}
@injectable()
export class AdministrationItemService {
items: IAdministrationItem[] = [];
itemActivating: boolean;
itemDeactivating: boolean;
private activationTask: IExecutor<IActivationData>;
private deActivationTask: IExecutor<IActivationData>;
constructor() {
makeObservable(this, {
items: observable,
itemActivating: observable,
itemDeactivating: observable,
});
this.itemActivating = false;
this.itemDeactivating = false;
this.activationTask = new Executor();
this.deActivationTask = new Executor();
this.activationTask
.addHandler(() => { this.itemActivating = true; })
.addHandler(this.activateHandler)
.addPostHandler(() => { this.itemActivating = false; });
this.deActivationTask
.addHandler(() => { this.itemDeactivating = true; })
.addHandler(this.deActivateHandler)
.addPostHandler(() => { this.itemDeactivating = false; });
}
getUniqueItems(configurationWizard: boolean): IAdministrationItem[] {
const items: IAdministrationItem[] = [];
const orderedByPriority = this.items.slice()
.sort((a, b) => {
if (a.name !== b.name) {
return a.name.localeCompare(b.name);
}
if (a.replace && b.replace) {
return (b.replace.priority ?? Number.MIN_SAFE_INTEGER) - (a.replace.priority ?? Number.MIN_SAFE_INTEGER);
}
return 0;
})
.filter(item => !item.replace?.condition || item.replace.condition(configurationWizard));
let lastItem: IAdministrationItem | null = null;
for (const item of orderedByPriority) {
if (lastItem?.name === item.name) {
continue;
}
items.push(item);
lastItem = item;
}
return items;
}
getActiveItems(configurationWizard: boolean): IAdministrationItem[] {
return this.items.filter(item =>
filterHiddenAdministrationItem(item)
return this.getUniqueItems(configurationWizard).filter(item =>
filterHiddenAdministrationItem(configurationWizard)(item)
&& filterConfigurationWizard(configurationWizard)(item)
).sort(orderAdministrationItems(configurationWizard));
}
@@ -42,7 +100,7 @@ export class AdministrationItemService {
return null;
}
const onlyActive = items.find(filterOnlyActive);
const onlyActive = items.find(filterOnlyActive(configurationWizard));
if (onlyActive) {
return onlyActive.name;
@@ -60,11 +118,7 @@ export class AdministrationItemService {
}
getItem(name: string, configurationWizard: boolean): IAdministrationItem | null {
const item = this.items.find(item => (
filterHiddenAdministrationItem(item)
&& filterConfigurationWizard(configurationWizard)(item)
&& item.name === name
));
const item = this.getActiveItems(configurationWizard).find(item => item.name === name);
if (!item) {
return null;
@@ -82,7 +136,7 @@ export class AdministrationItemService {
return sub;
}
create(options: IAdministrationItemOptions, replace?: boolean): void {
create(options: IAdministrationItemOptions): void {
const type = options.type ?? AdministrationItemType.Administration;
const existedIndex = this.items.findIndex(item => item.name === options.name && (
@@ -91,7 +145,7 @@ export class AdministrationItemService {
|| type === AdministrationItemType.Default
));
if (!replace && existedIndex !== -1) {
if (!options.replace && existedIndex !== -1) {
throw new Error(`Administration item "${options.name}" already exists in the same visibility scope`);
}
@@ -101,11 +155,7 @@ export class AdministrationItemService {
sub: options.sub ?? [],
order: options.order ?? Number.MAX_SAFE_INTEGER,
};
if (replace && existedIndex !== -1) {
this.items.splice(existedIndex, 1, item);
} else {
this.items.push(item);
}
this.items.push(item);
}
async activate(
@@ -113,20 +163,7 @@ export class AdministrationItemService {
configurationWizard: boolean,
outside: boolean
): Promise<void> {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
if (!filterHiddenAdministrationItem(item)) {
return;
}
await item.onActivate?.(configurationWizard, outside);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onActivate?.(screen.param, configurationWizard, outside);
}
await this.activationTask.execute({ screen, configurationWizard, outside });
}
async deActivate(
@@ -134,20 +171,7 @@ export class AdministrationItemService {
configurationWizard: boolean,
outside: boolean
): Promise<void> {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
if (!filterHiddenAdministrationItem(item)) {
return;
}
await item.onDeActivate?.(configurationWizard, outside);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onDeActivate?.(screen.param, configurationWizard, outside);
}
await this.deActivationTask.execute({ screen, configurationWizard, outside });
}
async canActivate(
@@ -160,10 +184,6 @@ export class AdministrationItemService {
return false;
}
if (!filterHiddenAdministrationItem(item)) {
return false;
}
if (item.canActivate && !(await item.canActivate(configurationWizard, outside))) {
return false;
}
@@ -177,20 +197,62 @@ export class AdministrationItemService {
return true;
}
private activateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
if (configurationWizard) {
let item = 0;
while (true) {
const items = this.getActiveItems(configurationWizard);
if (item === items.length) {
break;
}
await items[item].configurationWizardOptions?.onLoad?.();
item++;
}
}
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
await item.onActivate?.(configurationWizard, outside);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onActivate?.(screen.param, configurationWizard, outside);
}
};
private deActivateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
await item.onDeActivate?.(configurationWizard, outside);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onDeActivate?.(screen.param, configurationWizard, outside);
}
};
}
export function filterHiddenAdministrationItem(item: IAdministrationItem): boolean {
if (typeof item.isHidden === 'function') {
return !item.isHidden();
}
export function filterHiddenAdministrationItem(configurationWizard: boolean): (item: IAdministrationItem) => boolean {
return function filterHiddenAdministrationItem(item: IAdministrationItem) {
if (typeof item.isHidden === 'function') {
return !item.isHidden(configurationWizard);
}
return !item.isHidden;
return !item.isHidden;
};
}
export function filterOnlyActive(item: IAdministrationItem): boolean {
if (typeof item.isOnlyActive === 'function') {
return item.isOnlyActive();
}
export function filterOnlyActive(configurationWizard: boolean): (item: IAdministrationItem) => boolean {
return function filterOnlyActive(item: IAdministrationItem) {
if (typeof item.isOnlyActive === 'function') {
return item.isOnlyActive(configurationWizard);
}
return item.isOnlyActive === true;
return item.isOnlyActive === true;
};
}
@@ -16,6 +16,11 @@ export enum AdministrationItemType {
ConfigurationWizard
}
export interface IAdministrationItemReplaceOptions {
priority: number;
condition?: (configurationWizard: boolean) => boolean;
}
export interface AdministrationItemDrawerProps {
item: IAdministrationItem;
configurationWizard: boolean;
@@ -69,8 +74,9 @@ export interface IConfigurationWizardItemOptions {
isDisabled?: () => boolean;
isHidden?: () => boolean;
isDone?: () => boolean;
onLoad?: () => Promise<void> | void;
onFinish?: () => Promise<boolean> | boolean;
onConfigurationFinish?: () => Promise<void> | void;
onConfigurationFinish?: () => Promise<any> | any;
}
export interface IAdministrationItemOptions {
@@ -80,8 +86,9 @@ export interface IAdministrationItemOptions {
configurationWizardOptions?: IConfigurationWizardItemOptions;
order?: number;
sub?: IAdministrationItemSubItem[];
isHidden?: (() => boolean) | boolean;
isOnlyActive?: (() => boolean) | boolean;
isHidden?: ((configurationWizard: boolean) => boolean) | boolean;
isOnlyActive?: ((configurationWizard: boolean) => boolean) | boolean;
replace?: IAdministrationItemReplaceOptions;
getDrawerComponent: () => AdministrationItemDrawerComponent;
getContentComponent: () => AdministrationItemContentComponent;
onActivate?: AdministrationItemEvent;
@@ -13,6 +13,7 @@ import { IExecutor, Executor } from '@cloudbeaver/core-executor';
import { PermissionsResource, PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
import { ScreenService, RouterState } from '@cloudbeaver/core-routing';
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
import { BuildVersion } from '@cloudbeaver/core-utils';
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute';
@@ -20,10 +21,13 @@ import type { IRouteParams } from '../AdministrationItem/IRouteParams';
import { EAdminPermission } from '../EAdminPermission';
const ADMINISTRATION_ITEMS_STATE = 'administration_items_state';
const ADMINISTRATION_INFO = 'administration_mode';
const ADMINISTRATION_INFO = 'administration_info';
interface IAdministrationScreenInfo {
mode: boolean;
workspaceId: string;
version: string;
serverVersion: string;
configurationMode: boolean;
}
@injectable()
@@ -46,16 +50,11 @@ export class AdministrationScreenService {
}
get isConfigurationMode(): boolean {
return !!this.serverConfigResource.data?.configurationMode;
return this.serverConfigResource.configurationMode;
}
get publicDisabled(): boolean {
if (this.serverConfigResource.data?.configurationMode
|| (this.serverConfigResource.data?.licenseRequired && !this.serverConfigResource.data?.licenseValid)) {
return true;
}
return false;
return this.serverConfigResource.publicDisabled;
}
readonly ensurePermissions: IExecutor<void>;
@@ -76,7 +75,10 @@ export class AdministrationScreenService {
});
this.info = {
mode: false,
workspaceId: '',
version: BuildVersion.version || '',
serverVersion: '',
configurationMode: false,
};
this.itemState = new Map();
this.activationEvent = new Executor();
@@ -84,7 +86,7 @@ export class AdministrationScreenService {
this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE);
this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO);
this.permissionsResource.onDataUpdate.addHandler(() => {
this.permissionsResource.onDataUpdate.addPostHandler(() => {
this.checkPermissions(this.screenService.routerService.state);
});
}
@@ -140,16 +142,25 @@ export class AdministrationScreenService {
getItemState<T>(name: string): T | undefined
getItemState<T>(name: string, defaultState: () => T, update?: boolean, validate?: (state: T) => boolean): T
getItemState<T>(
name: string, defaultState?: () => T,
name: string,
defaultState?: () => T,
update?: boolean,
validate?: (state: T) => boolean
): T | undefined {
if (!this.serverConfigResource.isLoaded()) {
throw new Error('Administration screen getItemState can be used only after server configuration loaded');
}
if (this.info.mode !== this.isConfigurationMode) {
if (
this.info.workspaceId !== this.serverConfigResource.workspaceId
|| this.info.configurationMode !== this.isConfigurationMode
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|| this.info.version !== BuildVersion.version
) {
this.clearItemsState();
this.info.mode = this.isConfigurationMode;
this.info.workspaceId = this.serverConfigResource.workspaceId;
this.info.configurationMode = this.isConfigurationMode;
this.info.serverVersion = this.serverConfigResource.serverVersion;
this.info.version = BuildVersion.version || '';
}
if (defaultState) {
@@ -245,7 +256,7 @@ export class AdministrationScreenService {
return true;
}
private async isAccessProvided(state: RouterState) {
private async isAccessProvided(state: RouterState): Promise<boolean> {
await this.serverConfigResource.load();
if (this.isConfigurationMode) {
@@ -253,8 +264,7 @@ export class AdministrationScreenService {
}
if (this.screenService.isActive(state.name, AdministrationScreenService.setupName)) {
this.navigateToRoot();
return;
return false;
}
await this.ensurePermissions.execute();
@@ -11,7 +11,7 @@ import { computed, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ScreenService } from '@cloudbeaver/core-routing';
import { AdministrationItemService } from '../../AdministrationItem/AdministrationItemService';
import { AdministrationItemService, filterHiddenAdministrationItem } from '../../AdministrationItem/AdministrationItemService';
import { filterConfigurationWizard } from '../../AdministrationItem/filterConfigurationWizard';
import type { IAdministrationItem } from '../../AdministrationItem/IAdministrationItem';
import { orderAdministrationItems } from '../../AdministrationItem/orderAdministrationItems';
@@ -20,8 +20,11 @@ import { AdministrationScreenService } from '../AdministrationScreenService';
@injectable()
export class ConfigurationWizardService {
get steps(): IAdministrationItem[] {
return this.administrationItemService.items
.filter(filterConfigurationWizard(true))
return this.administrationItemService.getUniqueItems(true)
.filter(item =>
filterConfigurationWizard(true)(item)
&& filterHiddenAdministrationItem(true)(item)
)
.sort(orderAdministrationItems(true));
}
@@ -163,7 +166,7 @@ export class ConfigurationWizardService {
}
private getStep(name: string) {
return this.administrationItemService.items
return this.administrationItemService.getUniqueItems(true)
.find(step => filterConfigurationWizard(true)(step) && step.name === name);
}
@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="361" height="74" viewBox="0 0 361 74">
<defs>
<path id="prefix__a" d="M0 0.006L114.354 0.006 114.354 72.424 0 72.424z"/>
</defs>
<g fill="none" fill-rule="evenodd" transform="translate(0 .676)">
<path fill="#4598D3" d="M148.71 34.777V29.99c0-4.274-3.479-7.752-7.753-7.752h-5.865c-4.275 0-7.753 3.478-7.753 7.752v31.96c0 4.274 3.478 7.752 7.753 7.752h5.865c4.274 0 7.752-3.478 7.752-7.752v-3.069h-6.414v1.452c0 2.265-.86 3.663-2.955 3.663H136.708c-2.094 0-2.954-1.398-2.954-3.663V31.608c0-2.215.994-3.663 2.954-3.663h2.633c1.959 0 2.954 1.448 2.954 3.663v3.169h6.414zM164.41 69.674c-.26 0-.69.005-1.198.01-1.844.02-3.431.035-4.188-.017-3.238-.286-5.588-2.73-5.588-5.81V34.423h6.35s.08 19.396 0 27.48c-.02 2.122-.022 2.087 1.568 2.132 1.442.042 2.04.084 2.72.088 3.436.024 3.436 5.55 3.436 5.55h-3.1zM198.923 62.21v1.797h1.798c-.993 0-1.798-.805-1.798-1.798"/>
<path fill="#ffffff" d="M252.705 60.334c0 2.198-1.254 3.663-2.954 3.663h-5.393V46.669h5.007c1.884 0 3.34 1.314 3.34 3.599v10.066zm-8.347-32.389h4.04c1.84 0 2.953 1.515 2.953 3.663V37.3c0 2.533-.91 3.663-2.953 3.663h-4.04V27.945zm12.127 15.206c.828-1.266 1.282-2.732 1.282-4.234V29.99c0-4.26-3.36-7.665-7.688-7.751h-12.136s-.008 1.697 0 6.277c.02 10.751 0 41.186 0 41.186h13.853c1.076 0 2.425-.546 2.425-.546 2.913-1.181 4.9-4.002 4.9-7.206v-13.3c0-2.018-.99-4.018-2.636-5.5zM268.596 49.167v-7.451c.097-.801.72-1.439 1.515-1.558h2.917c.865.13 1.53.874 1.53 1.775v7.234h-5.962zm6.558-14.715H268c-3.173 0-5.754 2.61-5.754 5.818v23.68c0 3.172 2.581 5.753 5.754 5.753h12.974s0-5.697-4.283-5.706c-1.646-.004-4.667 0-6.468.002-.847-.074-1.525-.734-1.626-1.572v-7.554h12.377V40.27c0-3.208-2.61-5.818-5.819-5.818zM296.325 63.998L292.03 64c-.916-.065-1.64-.815-1.666-1.74l-.002-8.978c.002-.87.624-1.595 1.448-1.758h4.516v12.474zm-5.965-12.405v.724-.724zm6.562-17.14h-7.501c-4.388 0-4.446 5.705-4.446 5.705h9.833c.8.12 1.427.764 1.517 1.573v4.088h-6.558c-3.173 0-5.754 2.58-5.754 5.753V63.95c0 3.173 2.581 5.754 5.754 5.754h12.973V40.27c0-3.207-2.61-5.818-5.818-5.818zM313.564 55.635L309.588 34.452 303.432 34.452 309.979 69.703 316.999 69.703 323.546 34.452 317.586 34.452zM357.874 34.453c-.26 0-.691-.005-1.199-.011-1.844-.02-3.43-.034-4.188.018-3.238.286-5.588 2.73-5.588 5.81v29.433h6.35s.08-19.397 0-27.48c-.02-2.122-.022-2.087 1.568-2.133 1.442-.041 2.04-.083 2.72-.088 3.436-.023 3.436-5.55 3.436-5.55h-3.1zM331.317 49.167v-7.45c.097-.802.72-1.44 1.515-1.559h2.918c.865.13 1.529.874 1.53 1.775v7.234h-5.963zm6.559-14.715h-7.155c-3.173 0-5.754 2.61-5.754 5.818v23.68c0 3.172 2.581 5.753 5.754 5.753h12.973s0-5.697-4.282-5.706c-1.646-.004-4.668 0-6.468.002-.847-.074-1.525-.734-1.627-1.572v-7.554h12.377V40.27c0-3.208-2.61-5.818-5.818-5.818z"/>
<path fill="#4598D3" d="M182.228 49.167v13.041c0 .901-.664 1.645-1.529 1.775h-2.918c-.794-.12-1.417-.757-1.515-1.558v-20.71c.098-.8.721-1.438 1.515-1.557h2.918c.865.13 1.529.874 1.53 1.775v7.234zm.597-14.715h-3.59v-.014h-3.565c-3.173 0-5.754 2.581-5.754 5.754V63.95c0 3.172 2.581 5.753 5.754 5.753h3.566l-.002-.014h3.591c3.208 0 5.818-2.61 5.818-5.818V40.27c0-3.208-2.61-5.818-5.818-5.818zM204.854 34.453v27.755c0 .901-.664 1.645-1.529 1.775h-2.918c-.794-.12-1.418-.757-1.514-1.558V34.438h-6.35V63.95c0 3.173 2.58 5.754 5.753 5.754h3.566c0-.005 3.589-.014 3.589-.014 3.208 0 5.818-2.61 5.818-5.818V34.453h-6.415zM227.115 49.167v13.041c0 .901-.665 1.645-1.53 1.775h-4.432V40.158h4.433c.864.13 1.529.874 1.529 1.775v7.234zm.596-14.715h-3.59l.001-.014h-9.32v35.265h9.32l-.001-.014h3.59c3.209 0 5.819-2.61 5.819-5.818V40.27c0-3.208-2.61-5.818-5.819-5.818z"/>
<mask id="prefix__b" fill="#fff">
<use xlink:href="#prefix__a"/>
</mask>
<path fill="#4598D3" d="M94.575 35.503c-.581 0 0 0 0 0 0-18.976-16.87-35.497-35.846-35.497-13.978 0-26.446 8.45-31.812 20.432-.187-.004 0-.004-.187-.004C11.996 20.434 0 33.654 0 48.388c0 10.488 4.369 18.65 13.171 23.009 1.404-1.462 4.884-1.488 6.68-2.479-3.155-1.045-5.722-3.195-8.464-5.239-9.428-7.027-8.014-24.153-1.831-29.933 9.905-9.26 20.926-6.91 20.926-6.91l1.369-3.055c2.282-5.095 6.872-19.87 27.842-19.608 22.178.277 28.217 23.724 28.225 31.54l-.001.096-.012 5.523 5.502-.478c.436-.038.869-.057 1.286-.057 8.055 0 16.28 6.266 16.28 14.322 0 8.055-8.225 12.252-16.28 12.252H49.15c.519.265 5.77 5.053 14.419 5.053h31.124c10.859 0 19.661-6.16 19.661-17.019 0-10.858-8.92-19.902-19.78-19.902" mask="url(#prefix__b)"/>
<path fill="#4598D3" d="M80 24.64c-2.074-.777-7.403-.253-11.148 1.21-1.836.716-3.293 1.658-3.794 2.784-1.374 3.092 7.042 8.458 10.434 7.471 1.616-.47 3.937-3.91 4.972-6.92.735-2.14.82-4.062-.464-4.544M61.18 27.186c-.96-.122-2.947-.39-3.582-1.109-1.418-1.607-3.692-2.038-5.346-1.674-1.751.385-2.684 2.424-1.48 4.959.54 1.14.403 2.515-.148 3.54 1.722-1.604 3.332-2.912 4.527-3.54 2.354-1.238 4.442-1.88 6.03-2.176M78.369 50.697s-1.718 5.84-2.92 8.5c-1.06 2.347-1.395 2.16-2.161 2.435.288-.728.495-1.682.873-3.293.454-1.937 0-2.447 0-2.447-.596 2.9-1.521 4.97-2.308 6.33-1.02.341-2.275.562-3.188.196-1.288-.515-1.507-6.57-1.507-6.912 5.605.386 8.592-1.89 11.21-4.81m-27.555-6.382c.119.562.25 1.09.389 1.6l-.131-.148s.11.254.28.691c.235.805.495 1.541.773 2.222.922 2.96 1.905 7.505.44 10.355-1.096 2.13-15.017 9.102 8.777 13.282l21.36-4.945c-9.41-1.274-5.63-6.19-3.388-10.234 2.691-4.853 3.61-7.944 4.19-12.402.348-2.68.092-4.685.018-5.167-.011-.073-.019-.114-.019-.114s-.01.15-.038.407l.038-.664s-1.698 6.827-5.047 9.274c-.78.57-1.278.948-1.598 1.203.337-1.272 1.602-6.637-.206-9.576 0 0-.75 12.344-7.33 13-6.582.654-15.856-6.08-18.508-8.784"/>
<path fill="#4598D3" d="M31.876 43.906c-.264 1.478-.378 2.81-.337 3.982C30.26 49.916 21.026 71.87 9.556 62.005c1.963 1.72 3.615 9.392 3.615 9.392 10.788 6.304 18.259-18.42 19.112-20.042.695 1.486 1.87 4.503 8.39 4.637 0 0-7.65-3.75-4.235-13.7C39.852 32.34 44.9 23.895 51.83 19.99c6.93-3.905 13.932-3.638 20.373-.89 0 0-9.72-8.936-20.89-2.182-6.417 3.881-10.689 8.187-13.63 12.6-2.466.441-5.465.043-6.68.043-1.537 0-3.908 1.043-2.836 4.815 1.05 3.694 3.946 7.43 3.71 9.53"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="361" height="74" viewBox="0 0 361 74">
<defs>
<path id="prefix__a" d="M0 0.006L114.354 0.006 114.354 72.424 0 72.424z"/>
</defs>
<g fill="none" fill-rule="evenodd" transform="translate(0 .676)">
<path fill="#4598D3" d="M148.71 34.777V29.99c0-4.274-3.479-7.752-7.753-7.752h-5.865c-4.275 0-7.753 3.478-7.753 7.752v31.96c0 4.274 3.478 7.752 7.753 7.752h5.865c4.274 0 7.752-3.478 7.752-7.752v-3.069h-6.414v1.452c0 2.265-.86 3.663-2.955 3.663H136.708c-2.094 0-2.954-1.398-2.954-3.663V31.608c0-2.215.994-3.663 2.954-3.663h2.633c1.959 0 2.954 1.448 2.954 3.663v3.169h6.414zM164.41 69.674c-.26 0-.69.005-1.198.01-1.844.02-3.431.035-4.188-.017-3.238-.286-5.588-2.73-5.588-5.81V34.423h6.35s.08 19.396 0 27.48c-.02 2.122-.022 2.087 1.568 2.132 1.442.042 2.04.084 2.72.088 3.436.024 3.436 5.55 3.436 5.55h-3.1zM198.923 62.21v1.797h1.798c-.993 0-1.798-.805-1.798-1.798"/>
<path fill="#1E3C4F" d="M252.705 60.334c0 2.198-1.254 3.663-2.954 3.663h-5.393V46.669h5.007c1.884 0 3.34 1.314 3.34 3.599v10.066zm-8.347-32.389h4.04c1.84 0 2.953 1.515 2.953 3.663V37.3c0 2.533-.91 3.663-2.953 3.663h-4.04V27.945zm12.127 15.206c.828-1.266 1.282-2.732 1.282-4.234V29.99c0-4.26-3.36-7.665-7.688-7.751h-12.136s-.008 1.697 0 6.277c.02 10.751 0 41.186 0 41.186h13.853c1.076 0 2.425-.546 2.425-.546 2.913-1.181 4.9-4.002 4.9-7.206v-13.3c0-2.018-.99-4.018-2.636-5.5zM268.596 49.167v-7.451c.097-.801.72-1.439 1.515-1.558h2.917c.865.13 1.53.874 1.53 1.775v7.234h-5.962zm6.558-14.715H268c-3.173 0-5.754 2.61-5.754 5.818v23.68c0 3.172 2.581 5.753 5.754 5.753h12.974s0-5.697-4.283-5.706c-1.646-.004-4.667 0-6.468.002-.847-.074-1.525-.734-1.626-1.572v-7.554h12.377V40.27c0-3.208-2.61-5.818-5.819-5.818zM296.325 63.998L292.03 64c-.916-.065-1.64-.815-1.666-1.74l-.002-8.978c.002-.87.624-1.595 1.448-1.758h4.516v12.474zm-5.965-12.405v.724-.724zm6.562-17.14h-7.501c-4.388 0-4.446 5.705-4.446 5.705h9.833c.8.12 1.427.764 1.517 1.573v4.088h-6.558c-3.173 0-5.754 2.58-5.754 5.753V63.95c0 3.173 2.581 5.754 5.754 5.754h12.973V40.27c0-3.207-2.61-5.818-5.818-5.818zM313.564 55.635L309.588 34.452 303.432 34.452 309.979 69.703 316.999 69.703 323.546 34.452 317.586 34.452zM357.874 34.453c-.26 0-.691-.005-1.199-.011-1.844-.02-3.43-.034-4.188.018-3.238.286-5.588 2.73-5.588 5.81v29.433h6.35s.08-19.397 0-27.48c-.02-2.122-.022-2.087 1.568-2.133 1.442-.041 2.04-.083 2.72-.088 3.436-.023 3.436-5.55 3.436-5.55h-3.1zM331.317 49.167v-7.45c.097-.802.72-1.44 1.515-1.559h2.918c.865.13 1.529.874 1.53 1.775v7.234h-5.963zm6.559-14.715h-7.155c-3.173 0-5.754 2.61-5.754 5.818v23.68c0 3.172 2.581 5.753 5.754 5.753h12.973s0-5.697-4.282-5.706c-1.646-.004-4.668 0-6.468.002-.847-.074-1.525-.734-1.627-1.572v-7.554h12.377V40.27c0-3.208-2.61-5.818-5.818-5.818z"/>
<path fill="#4598D3" d="M182.228 49.167v13.041c0 .901-.664 1.645-1.529 1.775h-2.918c-.794-.12-1.417-.757-1.515-1.558v-20.71c.098-.8.721-1.438 1.515-1.557h2.918c.865.13 1.529.874 1.53 1.775v7.234zm.597-14.715h-3.59v-.014h-3.565c-3.173 0-5.754 2.581-5.754 5.754V63.95c0 3.172 2.581 5.753 5.754 5.753h3.566l-.002-.014h3.591c3.208 0 5.818-2.61 5.818-5.818V40.27c0-3.208-2.61-5.818-5.818-5.818zM204.854 34.453v27.755c0 .901-.664 1.645-1.529 1.775h-2.918c-.794-.12-1.418-.757-1.514-1.558V34.438h-6.35V63.95c0 3.173 2.58 5.754 5.753 5.754h3.566c0-.005 3.589-.014 3.589-.014 3.208 0 5.818-2.61 5.818-5.818V34.453h-6.415zM227.115 49.167v13.041c0 .901-.665 1.645-1.53 1.775h-4.432V40.158h4.433c.864.13 1.529.874 1.529 1.775v7.234zm.596-14.715h-3.59l.001-.014h-9.32v35.265h9.32l-.001-.014h3.59c3.209 0 5.819-2.61 5.819-5.818V40.27c0-3.208-2.61-5.818-5.819-5.818z"/>
<mask id="prefix__b" fill="#fff">
<use xlink:href="#prefix__a"/>
</mask>
<path fill="#4598D3" d="M94.575 35.503c-.581 0 0 0 0 0 0-18.976-16.87-35.497-35.846-35.497-13.978 0-26.446 8.45-31.812 20.432-.187-.004 0-.004-.187-.004C11.996 20.434 0 33.654 0 48.388c0 10.488 4.369 18.65 13.171 23.009 1.404-1.462 4.884-1.488 6.68-2.479-3.155-1.045-5.722-3.195-8.464-5.239-9.428-7.027-8.014-24.153-1.831-29.933 9.905-9.26 20.926-6.91 20.926-6.91l1.369-3.055c2.282-5.095 6.872-19.87 27.842-19.608 22.178.277 28.217 23.724 28.225 31.54l-.001.096-.012 5.523 5.502-.478c.436-.038.869-.057 1.286-.057 8.055 0 16.28 6.266 16.28 14.322 0 8.055-8.225 12.252-16.28 12.252H49.15c.519.265 5.77 5.053 14.419 5.053h31.124c10.859 0 19.661-6.16 19.661-17.019 0-10.858-8.92-19.902-19.78-19.902" mask="url(#prefix__b)"/>
<path fill="#4598D3" d="M80 24.64c-2.074-.777-7.403-.253-11.148 1.21-1.836.716-3.293 1.658-3.794 2.784-1.374 3.092 7.042 8.458 10.434 7.471 1.616-.47 3.937-3.91 4.972-6.92.735-2.14.82-4.062-.464-4.544M61.18 27.186c-.96-.122-2.947-.39-3.582-1.109-1.418-1.607-3.692-2.038-5.346-1.674-1.751.385-2.684 2.424-1.48 4.959.54 1.14.403 2.515-.148 3.54 1.722-1.604 3.332-2.912 4.527-3.54 2.354-1.238 4.442-1.88 6.03-2.176M78.369 50.697s-1.718 5.84-2.92 8.5c-1.06 2.347-1.395 2.16-2.161 2.435.288-.728.495-1.682.873-3.293.454-1.937 0-2.447 0-2.447-.596 2.9-1.521 4.97-2.308 6.33-1.02.341-2.275.562-3.188.196-1.288-.515-1.507-6.57-1.507-6.912 5.605.386 8.592-1.89 11.21-4.81m-27.555-6.382c.119.562.25 1.09.389 1.6l-.131-.148s.11.254.28.691c.235.805.495 1.541.773 2.222.922 2.96 1.905 7.505.44 10.355-1.096 2.13-15.017 9.102 8.777 13.282l21.36-4.945c-9.41-1.274-5.63-6.19-3.388-10.234 2.691-4.853 3.61-7.944 4.19-12.402.348-2.68.092-4.685.018-5.167-.011-.073-.019-.114-.019-.114s-.01.15-.038.407l.038-.664s-1.698 6.827-5.047 9.274c-.78.57-1.278.948-1.598 1.203.337-1.272 1.602-6.637-.206-9.576 0 0-.75 12.344-7.33 13-6.582.654-15.856-6.08-18.508-8.784"/>
<path fill="#4598D3" d="M31.876 43.906c-.264 1.478-.378 2.81-.337 3.982C30.26 49.916 21.026 71.87 9.556 62.005c1.963 1.72 3.615 9.392 3.615 9.392 10.788 6.304 18.259-18.42 19.112-20.042.695 1.486 1.87 4.503 8.39 4.637 0 0-7.65-3.75-4.235-13.7C39.852 32.34 44.9 23.895 51.83 19.99c6.93-3.905 13.932-3.638 20.373-.89 0 0-9.72-8.936-20.89-2.182-6.417 3.881-10.689 8.187-13.63 12.6-2.466.441-5.465.043-6.68.043-1.537 0-3.908 1.043-2.836 4.815 1.05 3.694 3.946 7.43 3.71 9.53"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -14,6 +14,7 @@ const defaultSettings = {
'app.logViewer.maxLogRecords': 1000,
'app.logViewer.logBatchSize': 2000,
'app.logViewer.maxFailedRequests': 3,
// temporary limit for all nodes children in app
'app.navigationTree.childrenLimit': 500,
};
@@ -6,10 +6,11 @@
* you may not use this file except in compliance with the License.
*/
import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { isNavigatorViewSettingsEqual, CONNECTION_NAVIGATOR_VIEW_SETTINGS, NavigatorViewSettings } from '@cloudbeaver/core-root';
import type { NavNode } from '../shared/NodesManager/EntityTypes';
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
@@ -17,7 +18,7 @@ import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerServ
import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils';
@injectable()
export class NavigationTreeContextMenuService {
export class NavigationTreeContextMenuService extends Bootstrap {
static nodeContextType = 'NodeWithParent';
private static nodeViewMenuItemToken = 'nodeView';
private static menuToken = 'navTreeMenu';
@@ -27,7 +28,9 @@ export class NavigationTreeContextMenuService {
private navNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService,
private connectionInfoResource: ConnectionInfoResource
) { }
) {
super();
}
getMenuToken(): string {
return NavigationTreeContextMenuService.menuToken;
@@ -80,6 +83,10 @@ export class NavigationTreeContextMenuService {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
isHidden: context => {
const connection = this.getConnectionFromNodeId(context.data.id);
return !connection?.connected;
},
order: 2,
title: 'app_navigationTree_connection_view',
isPanel: true,
@@ -144,7 +151,7 @@ export class NavigationTreeContextMenuService {
);
}
registerMenuItems() {
register(): void {
this.contextMenuService.addMenuItem<NavNode>(
this.contextMenuService.getRootMenuToken(),
{
@@ -170,13 +177,19 @@ export class NavigationTreeContextMenuService {
},
order: Number.MAX_SAFE_INTEGER,
title: 'app_navigationTree_refreshNode',
onClick: context => {
onClick: async context => {
const node = context.data;
this.navNodeManagerService.refreshTree(node.id);
try {
await this.navNodeManagerService.refreshTree(node.id);
} catch (exception) {
this.notificationService.logException(exception, 'Failed to refresh node');
}
},
}
);
this.registerNodeViewMenuItem();
}
load(): void { }
}
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { useCallback, useContext } from 'react';
import styled, { css } from 'reshadow';
import { TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks';
@@ -71,8 +71,12 @@ export const NavigationNodeControl: React.FC<Props> = observer(function Navigati
connected = !!connectionInfo?.connected;
}
const onClickHandler = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
context?.select(event.ctrlKey || event.metaKey);
}, [context]);
return styled(useStyles(TREE_NODE_STYLES, styles))(
<TreeNodeControl>
<TreeNodeControl onClick={onClickHandler}>
<TreeNodeExpand />
<TreeNodeIcon icon={node.icon}>
{connected && <status as='div' />}
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { action, computed, makeObservable } from 'mobx';
import { action, makeObservable } from 'mobx';
import { ConnectionAuthService } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
@@ -39,10 +39,6 @@ export class NavigationTreeService {
readonly navigationTreeMetadata: MetadataMap<string, INavigationNodeMetadata>;
readonly nodeSelectionTask: IExecutor<INavigationNodeSelectionData>;
get childrenLimit(): number {
return this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit');
}
constructor(
private navNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService,
@@ -53,7 +49,6 @@ export class NavigationTreeService {
private navNodeInfoResource: NavNodeInfoResource
) {
makeObservable<NavigationTreeService, 'unselectAll'>(this, {
childrenLimit: computed,
unselectAll: action,
});
@@ -77,13 +72,7 @@ export class NavigationTreeService {
}
getChildren(id: string): string[] | undefined {
const children = this.navTreeResource.get(id);
if (children) {
return children.slice(0, this.childrenLimit);
}
return children;
return this.navTreeResource.get(id);
}
async navToNode(id: string, parentId: string): Promise<void> {
@@ -92,7 +81,7 @@ export class NavigationTreeService {
async loadNestedNodes(id = ROOT_NODE_PATH): Promise<boolean> {
try {
if (this.isConnectionNode(id) && !await this.tryInitConnection(id)) {
if (this.isConnectionNode(id) && !(await this.tryInitConnection(id))) {
return false;
}
await this.navNodeManagerService.loadTree(id);
@@ -158,10 +147,11 @@ export class NavigationTreeService {
return node?.objectFeatures.includes(EObjectFeature.dataSource);
}
private async tryInitConnection(navNodeId: string) {
private async tryInitConnection(navNodeId: string): Promise<boolean> {
const connection = await this.connectionAuthService.auth(
NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId));
NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId)
);
return connection.connected;
return connection?.connected || false;
}
}
@@ -8,7 +8,6 @@
import { computed, observable, makeObservable } from 'mobx';
import { AppAuthService } from '@cloudbeaver/core-authentication';
import {
ConnectionInfoResource,
ConnectionsManagerService,
@@ -17,9 +16,10 @@ import {
isConnectionProvider, IConnectionProvider,
isConnectionSetter, IConnectionSetter
} from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ExtensionUtils, IExtension } from '@cloudbeaver/core-extensions';
import { SessionDataResource } from '@cloudbeaver/core-root';
import type { ITab } from '../../shared/NavigationTabs/ITab';
import { NavigationTabsService } from '../../shared/NavigationTabs/NavigationTabsService';
@@ -45,7 +45,7 @@ interface IActiveItem<T> {
}
@injectable()
export class ConnectionSchemaManagerService {
export class ConnectionSchemaManagerService extends Bootstrap {
get currentConnectionId(): string | null | undefined {
if (!this.activeItem?.getCurrentConnectionId) {
return null;
@@ -125,8 +125,9 @@ export class ConnectionSchemaManagerService {
private connectionsManagerService: ConnectionsManagerService,
private dbDriverResource: DBDriverResource,
private notificationService: NotificationService,
private appAuthService: AppAuthService
private sessionDataResource: SessionDataResource
) {
super();
makeObservable<ConnectionSchemaManagerService, 'activeItem' | 'activeItemHistory'>(this, {
currentObjectCatalog: computed,
currentObjectSchema: computed,
@@ -136,16 +137,19 @@ export class ConnectionSchemaManagerService {
});
}
registerCallbacks(): void {
register(): void {
this.sessionDataResource.onDataUpdate
.addHandler(this.reset.bind(this));
this.navigationTabsService.onTabSelect
.subscribe(this.onTabSelect.bind(this));
this.navigationTabsService.onTabClose
.subscribe(this.onTabClose.bind(this));
this.appAuthService.auth.addHandler(() => this.reset());
}
load(): void {}
/**
* Trigger when user select connection in dropdown
*/
@@ -15,6 +15,10 @@ export const topMenuStyles = composes(
Menu {
composes: theme-text-on-primary from global;
}
checkbox {
composes: theme-checkbox_surface from global;
}
`,
css`
Menu {
@@ -6,13 +6,14 @@
* you may not use this file except in compliance with the License.
*/
import { observable, action, computed, IKeyValueMap, makeObservable } from 'mobx';
import { observable, action, computed, makeObservable } from 'mobx';
import { Subject } from 'rxjs';
import { AdministrationScreenService } from '@cloudbeaver/core-administration';
import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
import type { IActiveView } from '@cloudbeaver/core-view';
@@ -68,6 +69,7 @@ export class NavigationTabsService {
private autoSaveService: LocalStorageSaveService,
private userInfoResource: UserInfoResource,
private administrationScreenService: AdministrationScreenService,
sessionDataResource: SessionDataResource,
appAuthService: AppAuthService
) {
makeObservable<NavigationTabsService, 'unloadTabs'>(this, {
@@ -120,10 +122,10 @@ export class NavigationTabsService {
}
);
appAuthService.auth
sessionDataResource.onDataUpdate
.addHandler(() => this.unloadTabs())
.addPostHandler(async state => {
if (state || appAuthService.authenticated) {
.addPostHandler(async () => {
if (appAuthService.authenticated) {
await this.restoreTabs();
}
});
@@ -313,6 +315,10 @@ export class NavigationTabsService {
}
private async restoreTab(tab: ITab, removedTabs: string[]): Promise<void> {
if (tab.restored) {
return;
}
let restoreFail = false;
try {
const handler = this.handlers.get(tab.handlerId);
@@ -7,8 +7,9 @@
*/
import { ConnectionInfoResource, ConnectionsManagerService, EConnectionFeature } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IMenuContext } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { NavigationTreeContextMenuService } from '../../NavigationTree/NavigationTreeContextMenuService';
import { EMainMenu, MainMenuService } from '../../TopNavBar/MainMenu/MainMenuService';
@@ -17,15 +18,18 @@ import { EObjectFeature } from './EObjectFeature';
import { NodeManagerUtils } from './NodeManagerUtils';
@injectable()
export class ConnectionDialogsService {
export class ConnectionDialogsService extends Bootstrap {
constructor(
private mainMenuService: MainMenuService,
private contextMenuService: ContextMenuService,
private connectionsManagerService: ConnectionsManagerService,
private connectionInfoResource: ConnectionInfoResource
) {}
private connectionInfoResource: ConnectionInfoResource,
private notificationService: NotificationService,
) {
super();
}
registerMenuItems(): void {
register(): void {
this.mainMenuService.registerMenuItem(
EMainMenu.mainMenuConnectionsPanel,
{
@@ -74,13 +78,19 @@ export class ConnectionDialogsService {
|| !connection?.features.includes(EConnectionFeature.manageable);
},
title: 'ui_delete',
onClick: (context: IMenuContext<NavNode>) => {
onClick: async (context: IMenuContext<NavNode>) => {
const node = context.data;
this.connectionsManagerService.deleteConnection(
NodeManagerUtils.connectionNodeIdToConnectionId(node.id)
);
try {
await this.connectionsManagerService.deleteConnection(
NodeManagerUtils.connectionNodeIdToConnectionId(node.id)
);
} catch (exception) {
this.notificationService.logException(exception, 'Failed to delete connection');
}
},
}
);
}
load(): void { }
}
@@ -44,6 +44,7 @@ export class NavNodeInfoResource extends CachedMapResource<string, NavNode> {
outdated: true,
loading: false,
withDetails: false,
exception: null,
includes: [],
}));
}
@@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
import { AppAuthService } from '@cloudbeaver/core-authentication';
import {
ConnectionAuthService, Connection, ConnectionInfoResource
} from '@cloudbeaver/core-connections';
@@ -14,7 +13,7 @@ import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { IExecutor, Executor, IExecutionContextProvider } from '@cloudbeaver/core-executor';
import {
PermissionsService, EPermission, ServerService
PermissionsService, EPermission, ServerService, SessionDataResource
} from '@cloudbeaver/core-root';
import {
GraphQLService, resourceKeyList, ResourceKey, ResourceKeyUtils
@@ -101,7 +100,7 @@ export class NavNodeManagerService extends Bootstrap {
private connectionAuthService: ConnectionAuthService,
private notificationService: NotificationService,
private serverService: ServerService,
private appAuthService: AppAuthService,
private sessionDataResource: SessionDataResource,
navigationService: NavigationService
) {
super();
@@ -118,7 +117,7 @@ export class NavNodeManagerService extends Bootstrap {
}
register(): void {
this.appAuthService.auth.addHandler(this.refreshRoot.bind(this));
this.sessionDataResource.onDataUpdate.addHandler(this.refreshRoot.bind(this));
this.connectionInfo.onSessionUpdate.addHandler(this.connectionRefreshHandler.bind(this));
this.connectionInfo.onItemAdd.addHandler(this.connectionUpdateHandler.bind(this));
this.connectionInfo.onItemDelete.addHandler(this.connectionRemoveHandler.bind(this));
@@ -420,7 +419,7 @@ export class NavNodeManagerService extends Bootstrap {
const nodeInfo = await contexts.getContext(this.navigationNavNodeContext);
if (NodeManagerUtils.isDatabaseObject(nodeInfo.nodeId) && nodeInfo.connection) {
let connection: Connection | undefined;
let connection: Connection | null;
try {
connection = await this.connectionAuthService.auth(nodeInfo.connection.id);
} catch (exception) {
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { action, makeObservable } from 'mobx';
import { action, computed, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import {
@@ -22,6 +22,7 @@ import {
} from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { CoreSettingsService } from '../../CoreSettingsService';
import { NavNodeInfoResource } from './NavNodeInfoResource';
// TODO: so much dirty
@@ -39,13 +40,19 @@ interface INodeMetadata extends ICachedMapResourceMetadata {
export class NavTreeResource extends CachedMapResource<string, string[]> {
protected metadata: MetadataMap<string, INodeMetadata>;
get childrenLimit(): number {
return this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit');
}
constructor(
private graphQLService: GraphQLService,
private navNodeInfoResource: NavNodeInfoResource
private navNodeInfoResource: NavNodeInfoResource,
private coreSettingsService: CoreSettingsService,
) {
super();
makeObservable(this, {
childrenLimit: computed,
setDetails: action,
});
@@ -53,6 +60,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
outdated: true,
loading: false,
withDetails: false,
exception: null,
includes: [],
}));
this.onDataOutdated.addHandler(navNodeInfoResource.markOutdated.bind(navNodeInfoResource));
@@ -241,6 +249,6 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
withDetails: metadata.withDetails,
});
return { navNodeChildren, navNodeInfo, parentPath };
return { navNodeChildren: navNodeChildren.slice(0, this.childrenLimit), navNodeInfo, parentPath };
}
}
@@ -9,7 +9,7 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { Icon } from '@cloudbeaver/core-blocks';
import { Link } from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { useStyles } from '@cloudbeaver/core-theming';
@@ -26,25 +26,15 @@ const style = css`
align-items: center;
}
message {
flex: 1;
word-break: break-word;
white-space: nowrap;
overflow: hidden;
padding-right: 16px;
text-overflow: ellipsis;
}
snack {
display: flex;
Link:hover {
cursor: pointer;
}
snack Icon {
display: block;
width: 16px;
height: 10px;
fill: rgba(255, 255, 255, 0);
}
tr:hover Icon {
fill: #338fcc;
@@ -61,11 +51,8 @@ export const LogEntry = observer(function LogEntry({ item }: LogEntryProps) {
<td>
<message-cell as="div">
<message as="div">
{item.message}
{item.stackTrace ? <Link onClick={controller.showDetails}>{item.message}</Link> : item.message}
</message>
<snack as="div" onClick={controller.showDetails}>
<Icon name="snack" viewBox="0 0 16 10" />
</snack>
</message-cell>
</td>
</tr>
@@ -6,23 +6,28 @@
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { EMainMenu, MainMenuService } from '../../../TopNavBar/MainMenu/MainMenuService';
import { LogViewerService } from './LogViewerService';
@injectable()
export class LogViewerMenuService {
constructor(private mainMenuService: MainMenuService,
private logViewerService: LogViewerService) {
export class LogViewerMenuService extends Bootstrap {
constructor(
private mainMenuService: MainMenuService,
private logViewerService: LogViewerService
) {
super();
}
registerMenuItems() {
register(): void {
this.mainMenuService.registerMenuItem(
EMainMenu.mainMenuToolsPanel,
{
id: 'openLogViewer',
order: 1,
type: 'checkbox',
isChecked: () => this.logViewerService.isActive,
title: 'app_shared_toolsMenu_logViewer',
onClick: () => this.toggleLogViewer(),
}
@@ -32,4 +37,6 @@ export class LogViewerMenuService {
toggleLogViewer() {
this.logViewerService.toggle();
}
load(): void {}
}
@@ -8,19 +8,19 @@
import { useService } from '@cloudbeaver/core-di';
import { ServerService } from '@cloudbeaver/core-root';
import { BuildVersion } from '@cloudbeaver/core-utils';
interface IAppVersion {
backendVersion: string;
frontendVersion: string;
}
declare const _VERSION_: string | undefined;
const VERSION_REGEX = /(\d+\.\d+\.\d+)/;
export function useAppVersion(short = false): IAppVersion {
const serverService = useService(ServerService);
let backendVersion = serverService.config.data?.version || '';
let frontendVersion = _VERSION_ || '';
let frontendVersion = BuildVersion.version || '';
if (short) {
backendVersion = VERSION_REGEX.exec(backendVersion)?.[1] ?? backendVersion;
@@ -0,0 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export const AUTH_PROVIDER_LOCAL_ID = 'local';
@@ -8,7 +8,7 @@
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { ServerService, SessionService } from '@cloudbeaver/core-root';
import { ServerService } from '@cloudbeaver/core-root';
import { UserInfoResource } from './UserInfoResource';
@@ -24,14 +24,14 @@ export class AppAuthService extends Bootstrap {
}
readonly auth: IExecutor<boolean>;
constructor(
private serverService: ServerService,
private sessionService: SessionService,
private userInfoResource: UserInfoResource,
) {
super();
this.auth = new Executor();
this.sessionService.session.onDataUpdate.addHandler(async () => { await this.authUser(); });
this.userInfoResource.onDataUpdate.addHandler(this.authUser.bind(this));
}
async isAuthNeeded(): Promise<boolean> {
@@ -7,7 +7,7 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import type { UserAuthInfo } from '@cloudbeaver/core-sdk';
import type { UserInfo } from '@cloudbeaver/core-sdk';
import { UserInfoResource } from './UserInfoResource';
@@ -17,12 +17,12 @@ export class AuthInfoService {
private userInfoResource: UserInfoResource,
) { }
get userInfo(): UserAuthInfo | null {
get userInfo(): UserInfo | null {
return this.userInfoResource.data;
}
async login(provider: string, credentials: Record<string, string>): Promise<UserAuthInfo> {
return this.userInfoResource.login(provider, credentials);
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
return this.userInfoResource.login(provider, credentials, link);
}
async logout(): Promise<void> {
@@ -7,19 +7,40 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import type { ObjectOrigin } from '@cloudbeaver/core-sdk';
import { md5 } from '@cloudbeaver/core-utils';
import { AuthProvidersResource } from './AuthProvidersResource';
@injectable()
export class AuthProviderService {
readonly requestAuthProvider: IExecutor<ObjectOrigin>;
constructor(
private providers: AuthProvidersResource
) { }
private readonly authProvidersResource: AuthProvidersResource
) {
this.requestAuthProvider = new Executor();
}
async requireProvider(type: string, subType?: string): Promise<boolean>
async requireProvider(origin: ObjectOrigin): Promise<boolean>
async requireProvider(origin: ObjectOrigin | string, subType?: string): Promise<boolean> {
if (typeof origin === 'string') {
origin = {
displayName: '',
type: origin,
subType,
};
}
const contexts = await this.requestAuthProvider.execute(origin);
const provider = contexts.getContext(AuthProviderContext);
return provider.get();
}
async processCredentials(providerId: string, credentials: Record<string, any>): Promise<Record<string, any>> {
const providers = await this.providers.load();
const provider = providers.find(provider => provider.id === providerId);
const provider = await this.authProvidersResource.load(providerId);
if (!provider) {
return credentials;
@@ -37,3 +58,19 @@ export class AuthProviderService {
return credentialsProcessed;
}
}
interface IAuthProviderContext{
get: () => boolean;
auth: () => void;
}
export function AuthProviderContext(): IAuthProviderContext {
let state = false;
return {
get: () => state,
auth: () => {
state = true;
},
};
}
@@ -7,28 +7,92 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { PermissionsResource } from '@cloudbeaver/core-root';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
CachedDataResource,
AuthProviderInfo
AuthProviderInfo,
CachedMapResource,
ResourceKey,
resourceKeyList,
ResourceKeyUtils
} from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>;
export type AuthProvider = AuthProviderInfo;
@injectable()
export class AuthProvidersResource extends CachedDataResource<AuthProvider[], void> {
export class AuthProvidersResource extends CachedMapResource<string, AuthProvider, void> {
static keyAll = resourceKeyList(['all']);
private loadedKeyMetadata: MetadataMap<string, boolean>;
constructor(
private graphQLService: GraphQLService,
private permissionsResource: PermissionsResource
private serverConfigResource: ServerConfigResource
) {
super([]);
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
this.permissionsResource.onDataUpdate.addHandler(() => this.markOutdated());
this.serverConfigResource.onDataOutdated.addHandler(() => this.markOutdated());
this.serverConfigResource.onDataUpdate.addHandler(async () => { await this.load(AuthProvidersResource.keyAll); });
this.addAlias(AuthProvidersResource.keyAll, key => {
if (this.keys.length > 0) {
return resourceKeyList(this.keys);
}
return key;
});
}
protected async loader(): Promise<AuthProvider[]> {
has(id: string): boolean {
if (this.loadedKeyMetadata.has(id)) {
return this.loadedKeyMetadata.get(id);
}
return this.data.has(id);
}
getEnabledProviders(): AuthProvider[] {
return this.get(resourceKeyList(this.serverConfigResource.enabledAuthProviders)) as AuthProvider[];
}
isEnabled(id: string): boolean {
return this.serverConfigResource.enabledAuthProviders.includes(id);
}
async loadAll(): Promise<AuthProvider[]> {
this.resetIncludes();
await this.load(AuthProvidersResource.keyAll);
return this.values;
}
async refreshAll(): Promise<AuthProvider[]> {
this.resetIncludes();
await this.refresh(AuthProvidersResource.keyAll);
return this.values;
}
refreshAllLazy(): void {
this.resetIncludes();
this.markOutdated(AuthProvidersResource.keyAll);
this.loadedKeyMetadata.set(AuthProvidersResource.keyAll.list[0], false);
}
protected async loader(key: ResourceKey<string>): Promise<Map<string, AuthProvider>> {
const { providers } = await this.graphQLService.sdk.getAuthProviders();
return providers;
const all = ResourceKeyUtils.includes(AuthProvidersResource.keyAll, key);
if (all) {
this.data.clear();
}
for (const provider of providers) {
this.data.set(provider.id, provider as AuthProvider);
}
if (all) {
this.loadedKeyMetadata.set(AuthProvidersResource.keyAll.list[0], true);
}
return this.data;
}
}
@@ -7,61 +7,82 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { SessionResource } from '@cloudbeaver/core-root';
import { CachedDataResource, GraphQLService, UserAuthInfo } from '@cloudbeaver/core-sdk';
import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root';
import { CachedDataResource, GraphQLService, UserAuthToken, UserInfo } from '@cloudbeaver/core-sdk';
import { AuthProviderService } from './AuthProviderService';
@injectable()
export class UserInfoResource extends CachedDataResource<UserAuthInfo | null, void> {
export class UserInfoResource extends CachedDataResource<UserInfo | null, void> {
constructor(
private graphQLService: GraphQLService,
private authProviderService: AuthProviderService,
private sessionResource: SessionResource
private sessionResource: SessionResource,
private sessionDataResource: SessionDataResource
) {
super(null);
this.sessionResource.onDataOutdated.addHandler(() => this.markOutdated());
this.sessionResource.onDataUpdate.addHandler(async () => { await this.load(); });
this.sync(this.sessionResource);
this.sessionDataResource.beforeLoad.addHandler(() => this.load());
this.onDataOutdated.addHandler(() => this.sessionDataResource.markOutdated());
}
isLinked(provideId: string): boolean {
return this.data?.linkedAuthProviders.includes(provideId) || false;
}
getId(): string {
return this.data?.userId || 'anonymous';
}
async login(provider: string, credentials: Record<string, string>): Promise<UserAuthInfo> {
if (this.data) {
throw new Error('User already logged in');
hasToken(type: string, subType?: string): boolean {
if (!this.data) {
return false;
}
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
// TODO: will be changed due wrong origin in authTokens
return this.data.authTokens.some(token => token.origin.type === (subType ?? type))
|| this.data.authTokens.some(token => token.origin.type === type && token.origin.subType === subType);
}
const { user } = await this.graphQLService.sdk.authLogin({
provider,
credentials: processedCredentials,
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
await this.performUpdate(undefined, undefined, async () => {
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
// TODO: will be replaced with another function
const { authToken } = await this.graphQLService.sdk.authLogin({
provider,
credentials: processedCredentials,
linkUser: link,
customIncludeOriginDetails: true,
});
if (this.data === null || link) {
this.data = await this.loader();
} else {
this.data.authTokens.push(authToken as UserAuthToken);
}
});
this.data = user as UserAuthInfo;
await this.sessionDataResource.refresh();
await this.updateSession();
return this.data;
}
async logout(): Promise<void> {
if (this.data) {
await this.graphQLService.sdk.authLogout();
this.data = null;
await this.updateSession();
}
await this.performUpdate(undefined, undefined, async () => {
if (this.data) {
await this.graphQLService.sdk.authLogout();
this.data = null;
}
});
await this.sessionDataResource.refresh();
}
protected async loader(): Promise<UserAuthInfo | null> {
await this.sessionResource.load();
const { user } = await this.graphQLService.sdk.getSessionUser();
protected async loader(): Promise<UserInfo | null> {
const { user } = await this.graphQLService.sdk.getActiveUser({
customIncludeOriginDetails: true,
});
return (user as UserAuthInfo | null) ?? null;
}
private async updateSession() {
await this.sessionResource.refresh();
return (user as UserInfo | null) ?? null;
}
}
@@ -7,6 +7,7 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
CachedMapResource,
@@ -19,6 +20,7 @@ import {
} from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID';
import { AuthInfoService } from './AuthInfoService';
import { AuthProviderService } from './AuthProviderService';
@@ -42,11 +44,13 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
private loadedKeyMetadata: MetadataMap<string, boolean>;
constructor(
private graphQLService: GraphQLService,
private serverConfigResource: ServerConfigResource,
private authProviderService: AuthProviderService,
private authInfoService: AuthInfoService
) {
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
this.serverConfigResource.onDataUpdate.addHandler(this.refreshAllLazy.bind(this));
}
isNew(id: string): boolean {
@@ -72,7 +76,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
configurationParameters: {},
metaParameters: {},
origin: {
type: 'local',
type: AUTH_PROVIDER_LOCAL_ID,
displayName: 'Local',
},
};
@@ -131,11 +135,10 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
}
async updateCredentials(userId: string, credentials: Record<string, any>): Promise<void> {
const provider = 'local';
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
const processedCredentials = await this.authProviderService.processCredentials(AUTH_PROVIDER_LOCAL_ID, credentials);
await this.graphQLService.sdk.setUserCredentials({
providerId: provider,
providerId: AUTH_PROVIDER_LOCAL_ID,
userId,
credentials: processedCredentials,
});
@@ -188,8 +191,6 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
}
if (key === UsersResource.keyAll) {
// TODO: driverList must accept driverId, so we can update some drivers or all drivers,
// here we should check is it's was a full update
this.loadedKeyMetadata.set(UsersResource.keyAll, true);
}
});
@@ -209,5 +210,5 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
}
export function isLocalUser(user: AdminUser): boolean {
return user.origin.type === 'local';
return user.origin.type === AUTH_PROVIDER_LOCAL_ID;
}

Some files were not shown because too many files have changed in this diff Show More