mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'devel' into fix/colors
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+9
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+15
-4
@@ -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
|
||||
|
||||
+21
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+38
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+37
-21
@@ -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());
|
||||
|
||||
+23
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-1
@@ -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();
|
||||
|
||||
|
||||
+12
-7
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-5
@@ -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
|
||||
|
||||
+5
-2
@@ -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"),
|
||||
|
||||
+20
-10
@@ -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;
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
@@ -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
|
||||
}
|
||||
|
||||
-6
@@ -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;
|
||||
}
|
||||
|
||||
+14
-1
@@ -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!]!
|
||||
|
||||
}
|
||||
|
||||
+6
-6
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -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())
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
+62
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+27
-3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+121
-44
@@ -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
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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 });
|
||||
|
||||
+3
-1
@@ -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) {
|
||||
|
||||
+119
-57
@@ -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;
|
||||
|
||||
+27
-17
@@ -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();
|
||||
|
||||
+7
-4
@@ -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 { }
|
||||
}
|
||||
|
||||
+6
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -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
Reference in New Issue
Block a user