Merge branch 'dbeaver:devel' into fix/db2_i

This commit is contained in:
jjsilvan
2021-08-02 09:49:23 +02:00
committed by GitHub
716 changed files with 20876 additions and 12116 deletions
+91 -1
View File
@@ -22,6 +22,96 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### CloudBeaver 21.1.0 - 2021-07-15
Changes since 21.0.0
- Navigation tree:
- User's and shared connections are divided into different groups.
- Connection form:
- Read-only connections are marked in the connection form.
- It is possible to show and hide password in the connection dialog.
- Data editor:
- New actions are available from the context menu:
- filtering
- ordering
- cell editing.
- Support for different formats is added to the Value panel.
- Work with links is improved:
- Links to the web pages can be opened from the tables
- Links to the pictures are automatically transformed to the pictures on the Value panel.
- It is possible to edit Boolean values as checkboxes in the tables.
- Read-only columns and tables are marked in the result set.
- SQL editor:
- Query execution plan is added.
- Database logos are displayed in the top menu selector.
- Auto-complete for the SQL dialects works automatically.
- Log viewer:
- Detailed information for errors is opened on the panel.
- Administration part:
- Access management for users and connections is improved.
- It is possible to change the user password from the public part of CloudBeaver.
- New design for dialogs is implemented.
- Possibility to export data for users can be configured.
- A lot of UI fixes, enhancements and improvements are made.
### CloudBeaver 21.0.5 - 2021-06-30
- Query execution plan was added.
- Option to show/hide password in the connection dialog was added.
- Minor UI fixes and improvements.
### CloudBeaver 21.0.3 - 2021-06-11
- Filtering and Ordering from the context menu were added for the data editor .
- Read-only columns and result sets were marked in the data editor.
- Possibility to remove data export feature from UI was added.
- CloudBeaver docker image is based on Ubuntu Slim now.
- Minor UI fixes and improvements.
### CloudBeaver 21.0.2 - 2021-05-20
- User's and shared connections are divided to different groups in the navigation tree.
- New design for authentication and driver properties dialogs was applied.
- Management for administrators' accounts was improved.
- Minor UI fixes and improvements.
### CloudBeaver 21.0.1 - 2021-04-28
- Database logos were added to the top menu selector.
- New panel to show error details were added to Log Viewer.
- Read-only connections were marked in the connection details form.
- New design for dialogs was implemented.
- Minor UI fixes and improvements.
### CloudBeaver 21.0.0 - 2021-04-15
Changes since 20.0.0:
- Navigation tree:
- Simple and advanced view for database objects
- Show system objects option
- Connection form:
- New drivers: Oracle, SQL Server, Trino (Presto), Derby Server
- SSH support
- Editing for manual connections
- Custom fields for advanced connection settings
- Connection form redesign
- Fixes for the name duplicates and deletion of the template connections
- Data editor:
- Value panel for data review and editing
- Spatial data viewer
- Sorting indication for the data grid
- Performance improvements
- SQL editor:
- Improvements for autosuggest and highlighting
- Log viewer:
- Quick review for the error details
- All errors and warnings on the panel
- New page with the product information
- Administration part:
- Redesign for the authentication engine
- Server re-configuration without re-login
- Additional labels for the connections with SSH
- A lot of UI fixes, enhancements and improvements.
### 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.
@@ -36,7 +126,7 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
- Possibility to edit connections created manually in the public part of the application
- SSH support for connections in public part of the application
- Possibility to deny credentials saving
- Drivers for SQL Server, Oracle, DB2 LUW, Derby, Trino
- Drivers for MS SQL Server, Oracle, DB2 LUW, Derby, Trino (Presto SQL)
- Performance improvements for data grid
- A lot of minor UI fixes and improvements
+1 -4
View File
@@ -1,7 +1,4 @@
FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.7_10
RUN apt-get update
RUN apt-get install -y nano
FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-ubuntu-slim
COPY cloudbeaver /opt/cloudbeaver
+1 -1
View File
@@ -9,7 +9,7 @@
{
roleId: "user",
name: "User",
description: "Standard user.",
description: "Standard user",
permissions: [ "public" ]
}
]
+3
View File
@@ -21,6 +21,9 @@
plugin_data_spreadsheet_new: {
hidden: false
},
plugin_data_export: {
disabled: false
},
// Notifications config
core_events: {
notificationsPool: 5
@@ -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.25.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.33.qualifier
Bundle-Release-Date: 20210802
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.25-SNAPSHOT</version>
<version>1.0.33-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.6.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 21.1.1.qualifier
Bundle-Release-Date: 20210802
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.6-SNAPSHOT</version>
<version>21.1.1-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.22.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.30.qualifier
Bundle-Release-Date: 20210802
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -43,7 +43,7 @@
<driver id="oracle:oracle_thin"/>
<driver id="postgresql:postgres-jdbc"/>
<driver id="jaybird:jaybird"/>
<driver id="generic:yandex_clickhouse"/>
<driver id="clickhouse:yandex_clickhouse"/>
<driver id="generic:derby_server"/>
<driver id="generic:h2_embedded"/>
<driver id="generic:sqlite_jdbc"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.22-SNAPSHOT</version>
<version>1.0.30-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.6.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 21.1.1.qualifier
Bundle-Release-Date: 20210802
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.WebPlatformActivator
@@ -5,6 +5,7 @@
<extension-point id="io.cloudbeaver.service" name="Web services" schema="schema/io.cloudbeaver.service.exsd"/>
<extension-point id="io.cloudbeaver.driver" name="Web drivers" schema="schema/io.cloudbeaver.driver.exsd"/>
<extension-point id="io.cloudbeaver.valueSerializer" name="Web value serializers" schema="schema/io.cloudbeaver.valueSerializer.exsd"/>
<extension point="org.eclipse.core.runtime.adapters">
<factory adaptableType="org.jkiss.dbeaver.runtime.DBWorkbench" class="io.cloudbeaver.server.WebPlatformAdapterFactory">
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>20.0.6-SNAPSHOT</version>
<version>21.1.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -0,0 +1,34 @@
<?xml version='1.0' encoding='UTF-8'?>
<schema targetNamespace="org.jkiss.dbeaver.core" xmlns="http://www.w3.org/2001/XMLSchema">
<annotation>
<appInfo>
<meta.schema plugin="io.cloudbeaver.server" id="io.cloudbeaver.valueSerializer" name="Web value serializers"/>
</appInfo>
<documentation>
DBeaver web value serializers
</documentation>
</annotation>
<element name="extension">
<annotation>
<appInfo>
<meta.element />
</appInfo>
</annotation>
<complexType>
<sequence>
<element ref="serializer"/>
</sequence>
</complexType>
</element>
<element name="serializer">
<annotation>
<documentation>
DBeaver web value serializer
</documentation>
</annotation>
</element>
</schema>
@@ -90,6 +90,10 @@ type ServerConfig {
version: String!
workspaceId: ID!
serverURL: String!
rootURI: String!
anonymousAccessEnabled: Boolean
authenticationEnabled: Boolean @deprecated
supportsCustomConnections: Boolean
@@ -3,29 +3,31 @@
####################################################
type SQLDialectInfo {
name: String
name: String!
dataTypes: [ String ]
functions: [ String ]
reservedWords: [ String ]
quoteStrings: [ [ String ] ]
singleLineComments: [ String ]
multiLineComments: [ [ String ] ]
dataTypes: [ String ]!
functions: [ String ]!
reservedWords: [ String ]!
quoteStrings: [ [ String ] ]!
singleLineComments: [ String ]!
multiLineComments: [ [ String ] ]!
catalogSeparator: String
structSeparator: String
scriptDelimiter: String
supportsExplainExecutionPlan: Boolean!
}
type SQLCompletionProposal {
displayString: String
type: String
displayString: String!
type: String!
score: Int
replacementString: String
replacementOffset: Int
replacementLength: Int
replacementString: String!
replacementOffset: Int!
replacementLength: Int!
cursorPosition: Int
icon: String
@@ -40,6 +42,7 @@ type SQLCompletionProposal {
type SQLContextInfo {
id: ID!
connectionId: ID!
defaultCatalog: String
defaultSchema: String
@@ -80,8 +83,13 @@ type SQLResultColumn {
scale: Int
precision: Int
required: Boolean!
readOnly: Boolean!
readOnlyStatus: String
# Operations supported for this attribute
supportedOperations: [DataTypeLogicalOperation!]!
}
type DatabaseDocument {
@@ -112,8 +120,13 @@ type SQLQueryResults {
type SQLExecuteInfo {
# Status message
statusMessage: String
duration: Int
# Execute time (ms)
duration: Int!
# Actual conditions applied to query
filterText: String
# Results
results: [ SQLQueryResults! ]!
}
@@ -122,20 +135,59 @@ input SQLResultRow {
updateValues: Object
}
type DataTypeLogicalOperation {
id: ID!
expression: String!
argumentCount: Int
}
####################################################
# SQL Executio plan
####################################################
type SQLExecutionPlan {
query: String!
nodes: [SQLExecutionPlanNode!]!
}
type SQLExecutionPlanNode {
id: ID!
parentId: ID
kind: String!
name: String
type: String!
condition: String
description: String
properties: [ObjectPropertyInfo!]!
}
####################################################
# Query and Mutation
####################################################
extend type Query {
sqlDialectInfo( connectionId: ID! ): SQLDialectInfo
sqlListContexts( connectionId: ID! ): [ SQLContextInfo ]
# Lists SQL contexts for a connection (optional) or returns the particular context info
sqlListContexts( connectionId: ID, contextId: ID ): [ SQLContextInfo ]!
sqlCompletionProposals(
connectionId: ID!,
contextId: ID!,
query: String!,
position: Int!,
maxResults: Int
maxResults: Int,
simpleMode: Boolean
): [ SQLCompletionProposal ]
sqlSupportedOperations(
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
attributeIndex: Int!
): [DataTypeLogicalOperation!]!
}
extend type Mutation {
@@ -171,9 +223,9 @@ extend type Mutation {
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
# Row cell values (to identify row)
updateRow: [ Object ]!,
# Map<Int,Object>
# Values to update - Map<Int,Object>
updateValues: Object
): SQLExecuteInfo
@@ -191,4 +243,16 @@ extend type Mutation {
# Returns SQLExecuteInfo
asyncSqlExecuteResults(taskId: ID!): SQLExecuteInfo !
# Read data from table
asyncSqlExplainExecutionPlan(
connectionId: ID!,
contextId: ID!,
query: String!,
configuration: Object!
): AsyncTaskInfo!
# Returns SQLExecutionPlan
asyncSqlExplainExecutionPlanResult(taskId: ID!): SQLExecutionPlan !
}
@@ -45,6 +45,17 @@ public interface DBWAuthProviderExternal<AUTH_SESSION extends DBASession> extend
@NotNull Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
) throws DBException;
/**
* Returns new identifying credentials which can be used to find/create user in database
*/
@NotNull
Map<String, Object> authFederatedUser(
@NotNull DBRProgressMonitor monitor,
@NotNull Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
@NotNull String faProvider,
@NotNull Map<String, Object> faParameters // Passed federated auth parameters (e.g. assertion ID, session token, etc)
) throws DBException;
/**
* Validates that external user may be associated with local user
* @param userCredentials credentials from authExternalUser
@@ -85,6 +85,8 @@ public interface DBWSecurityController {
void createRole(WebRole role) throws DBCException;
void updateRole(WebRole role) throws DBCException;
void deleteRole(String roleId) throws DBCException;
///////////////////////////////////////////
@@ -71,10 +71,6 @@ public class WebServiceUtils {
return icon == null ? null : icon.getLocation();
}
public static String makeDriverFullId(DBPDriver driver) {
return driver.getProviderId() + ":" + driver.getId();
}
@NotNull
public static DBPDriver getDriverById(String id) throws DBWebException {
int divPos = id.indexOf(':');
@@ -236,7 +232,12 @@ public class WebServiceUtils {
}
}
public static void saveAuthProperties(DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration, Map<String, Object> authProperties, boolean saveCredentials) {
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials)
{
dataSourceContainer.setSavePassword(saveCredentials);
if (!saveCredentials) {
// Reset credentials
@@ -247,6 +248,9 @@ public class WebServiceUtils {
return;
}
}
if (!saveCredentials) {
configuration.setUserPassword(null);
}
{
// Read save credentials
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
@@ -18,14 +18,20 @@ package io.cloudbeaver.auth.provider;
import io.cloudbeaver.DBWAuthProviderExternal;
import io.cloudbeaver.model.user.WebUser;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.access.DBASession;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.utils.CommonUtils;
import java.util.Map;
/**
* Abstract external auth provider
*/
public abstract class AbstractExternalAuthProvider<SESSION extends DBASession> implements DBWAuthProviderExternal<SESSION> {
@Deprecated
protected void setUserOrigin(WebUser user, String type, String subType) {
user.setMetaParameter(META_AUTH_PROVIDER, type);
if (!CommonUtils.isEmpty(subType)) {
@@ -33,4 +39,9 @@ public abstract class AbstractExternalAuthProvider<SESSION extends DBASession> i
}
}
@NotNull
@Override
public Map<String, Object> authFederatedUser(@NotNull DBRProgressMonitor monitor, @NotNull Map<String, Object> providerConfig, @NotNull String faProvider, @NotNull Map<String, Object> faParameters) throws DBException {
throw new DBException("Federated authorization not supported");
}
}
@@ -77,4 +77,31 @@ public class LocalAuthProvider implements DBWAuthProvider<LocalAuthSession> {
return SecurityUtils.makeDigest(password);
}
public static boolean changeUserPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBException {
String userName = webSession.getUser().getUserId();
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 saved credentials");
}
if (CommonUtils.isEmpty(oldPassword)) {
throw new DBException("No user password provided");
}
String oldPasswordHash = WebAuthProviderPropertyEncryption.hash.encrypt(userName, oldPassword);
if (!storedPasswordHash.equals(oldPasswordHash)) {
throw new DBException("Invalid user name or password");
}
//String newPasswordHash = WebAuthProviderPropertyEncryption.hash.encrypt(userName, newPassword);
storedCredentials.put(CRED_PASSWORD, newPassword);
CBApplication.getInstance().getSecurityController().setUserCredentials(userName, authProvider, storedCredentials);
return true;
}
}
@@ -77,7 +77,7 @@ public class WebConnectionInfo {
@Property
public String getDriverId() {
return WebServiceUtils.makeDriverFullId(dataSourceContainer.getDriver());
return dataSourceContainer.getDriver().getFullId();
}
@Property
@@ -263,7 +263,7 @@ public class WebConnectionInfo {
// Fill session and user provided credentials
boolean hasContextCredentials = session.hasContextCredentials();
DBPConnectionConfiguration configWithAuth = new DBPConnectionConfiguration(dataSourceContainer.getConnectionConfiguration());
session.provideAuthParameters(dataSourceContainer, configWithAuth);
session.provideAuthParameters(session.getProgressMonitor(), dataSourceContainer, configWithAuth);
DBPPropertySource credentialsSource = authModel.createCredentialsSource(dataSourceContainer, configWithAuth);
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.model;
import io.cloudbeaver.WebServiceUtils;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.meta.Property;
@@ -40,7 +39,7 @@ public class WebDataSourceConfig {
this.id = ds.getId();
this.name = ds.getName();
this.description = ds.getDescription();
this.driverId = WebServiceUtils.makeDriverFullId(ds.getDriver());
this.driverId = ds.getDriver().getFullId();
this.host = ds.getConnectionConfiguration().getHostName();
this.port = ds.getConnectionConfiguration().getHostPort();
this.url = ds.getConnectionConfiguration().getUrl();
@@ -46,7 +46,7 @@ public class WebDatabaseDriverConfig {
public WebDatabaseDriverConfig(WebSession webSession, DBPDriver driver) {
this.webSession = webSession;
this.driver = driver;
this.id = WebServiceUtils.makeDriverFullId(driver);
this.id = driver.getFullId();
}
@Property
@@ -59,6 +59,16 @@ public class WebServerConfig {
return DBWorkbench.getPlatform().getWorkspace().getWorkspaceId();
}
@Property
public String getServerURL() {
return CommonUtils.notEmpty(application.getServerURL());
}
@Property
public String getRootURI() {
return CommonUtils.notEmpty(application.getRootURI());
}
@Property
public boolean isAnonymousAccessEnabled() {
return application.getAppConfiguration().isAnonymousAccessEnabled();
@@ -31,6 +31,7 @@ import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.access.DBASession;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
@@ -56,10 +57,13 @@ import org.jkiss.dbeaver.registry.ProjectMetadata;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -74,6 +78,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
private static final Log log = Log.getLog(WebSession.class);
private static final String ATTR_LOCALE = "locale";
private static final String SESSION_TEMP_COOKIE = "cb-session";
private static final AtomicInteger TASK_ID = new AtomicInteger();
@@ -407,7 +412,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
}
}
synchronized void updateInfo(HttpServletRequest request) {
synchronized void updateInfo(HttpServletRequest request, HttpServletResponse response) {
HttpSession httpSession = request.getSession();
this.lastAccessTime = System.currentTimeMillis();
this.lastRemoteAddr = request.getRemoteAddr();
@@ -431,6 +436,17 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
log.error("Error persisting web session", e);
}
}
{
long maxSessionIdleTime = CBApplication.getInstance().getMaxSessionIdleTime();
SimpleDateFormat sdf = new SimpleDateFormat(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Cookie sessionCookie = new Cookie(SESSION_TEMP_COOKIE, sdf.format(new Date(System.currentTimeMillis() + maxSessionIdleTime)));
sessionCookie.setMaxAge((int) (maxSessionIdleTime / 1000));
sessionCookie.setPath(CBApplication.getInstance().getRootURI());
//sessionCookie.setComment("CB session cookie");
response.addCookie(sessionCookie);
}
}
@Association
@@ -718,13 +734,13 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
// Auth credentials provider
// Adds auth properties passed from web (by user)
@Override
public boolean provideAuthParameters(DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration) {
public boolean provideAuthParameters(@NotNull DBRProgressMonitor monitor, @NotNull DBPDataSourceContainer dataSourceContainer, @NotNull DBPConnectionConfiguration configuration) {
try {
// Properties from nested auth sessions
// FIXME: we need to support multiple credential providers (e.g. multiple clouds).
DBAAuthCredentialsProvider nestedProvider = getAdapter(DBAAuthCredentialsProvider.class);
if (nestedProvider != null) {
if (!nestedProvider.provideAuthParameters(dataSourceContainer, configuration)) {
if (!nestedProvider.provideAuthParameters(monitor, dataSourceContainer, configuration)) {
return false;
}
}
@@ -25,6 +25,7 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.*;
@@ -65,21 +66,21 @@ public class WebSessionManager {
return false;
}
public boolean touchSession(@NotNull HttpServletRequest request) throws DBWebException {
WebSession webSession = getWebSession(request, false);
webSession.updateInfo(request);
public boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
WebSession webSession = getWebSession(request, response, false);
webSession.updateInfo(request, response);
return true;
}
public WebSession getWebSession(@NotNull HttpServletRequest request) throws DBWebException {
return getWebSession(request, true);
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
return getWebSession(request, response, true);
}
public WebSession getWebSession(@NotNull HttpServletRequest request, boolean errorOnNoFound) throws DBWebException {
return getWebSession(request, true, errorOnNoFound);
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, boolean errorOnNoFound) throws DBWebException {
return getWebSession(request, response, true, errorOnNoFound);
}
public WebSession getWebSession(HttpServletRequest request, boolean updateInfo, boolean errorOnNoFound) throws DBWebException {
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, boolean updateInfo, boolean errorOnNoFound) throws DBWebException {
HttpSession httpSession = request.getSession(true);
String sessionId = httpSession.getId();
WebSession webSession;
@@ -103,7 +104,7 @@ public class WebSessionManager {
if (updateInfo) {
// Update only once per request
if (!CommonUtils.toBoolean(request.getAttribute("sessionUpdated"))) {
webSession.updateInfo(request);
webSession.updateInfo(request, response);
request.setAttribute("sessionUpdated", true);
}
}
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.registry;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.server.CBApplication;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
@@ -63,7 +62,7 @@ public class WebDriverRegistry {
}
public boolean isDriverEnabled(DBPDriver driver) {
String driverId = WebServiceUtils.makeDriverFullId(driver);
String driverId = driver.getFullId();
if (webDrivers.contains(driverId)) {
String[] enabledDrivers = CBApplication.getInstance().getAppConfiguration().getEnabledDrivers();
if (enabledDrivers.length > 0 && !ArrayUtils.contains(enabledDrivers, driverId)) {
@@ -17,16 +17,15 @@
package io.cloudbeaver.registry;
import io.cloudbeaver.service.DBWServiceBinding;
import io.cloudbeaver.service.sql.DBWValueSerializer;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.registry.RegistryConstants;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
public class WebServiceRegistry {
@@ -50,6 +49,7 @@ public class WebServiceRegistry {
private DBWServiceBinding[] webServiceInstances;
private final Map<String, WebAuthProviderDescriptor> authProviders = new LinkedHashMap<>();
private final Map<String, WebValueSerializerDescriptor> valueSerializers = new HashMap<>();
private WebServiceRegistry() {
}
@@ -90,6 +90,15 @@ public class WebServiceRegistry {
}
}
webServiceInstances = instances.toArray(new DBWServiceBinding[0]);
{
IConfigurationElement[] extConfigs = registry.getConfigurationElementsFor(WebValueSerializerDescriptor.EXTENSION_ID);
for (IConfigurationElement ext : extConfigs) {
WebValueSerializerDescriptor descriptor = new WebValueSerializerDescriptor(ext);
valueSerializers.put(descriptor.getValueType(), descriptor);
}
}
}
public List<WebServiceDescriptor> getWebServices() {
@@ -125,4 +134,17 @@ public class WebServiceRegistry {
return authProviders.get(id);
}
public DBWValueSerializer<?> createValueSerializer(String valueType) {
WebValueSerializerDescriptor descriptor = valueSerializers.get(valueType);
if (descriptor == null) {
return null;
}
try {
return descriptor.createInstance();
} catch (DBException e) {
log.error("Error creating value '" + valueType + "' serializer", e);
return null;
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.registry;
import io.cloudbeaver.service.sql.DBWValueSerializer;
import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
/**
* Value serializer descriptor
*/
public class WebValueSerializerDescriptor extends AbstractDescriptor {
public static final String EXTENSION_ID = "io.cloudbeaver.valueSerializer"; //$NON-NLS-1$
private final IConfigurationElement cfg;
private final String valueType;
private final ObjectType implType;
public WebValueSerializerDescriptor(IConfigurationElement cfg) {
super(cfg);
this.cfg = cfg;
this.valueType = cfg.getAttribute("type");
this.implType = new ObjectType(cfg, "class");
}
@NotNull
public String getValueType() {
return valueType;
}
public DBWValueSerializer<?> createInstance() throws DBException {
return implType.createInstance(DBWValueSerializer.class);
}
}
@@ -19,6 +19,7 @@ package io.cloudbeaver.server;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebServiceRegistry;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
@@ -46,6 +47,7 @@ public class CBAppConfig {
private String[] enabledAuthProviders = null;
private DataSourceNavigatorSettings defaultNavigatorSettings = DEFAULT_VIEW_SETTINGS;
private Map<String, Object> plugins = new LinkedHashMap<>();
private Map<String, Object> authConfiguration = new LinkedHashMap<>();
public boolean isAnonymousAccessEnabled() {
return anonymousAccessEnabled;
@@ -132,15 +134,16 @@ public class CBAppConfig {
this.defaultNavigatorSettings = new DataSourceNavigatorSettings(defaultNavigatorSettings);
}
@NotNull
public Map<String, Object> getPlugins() {
return plugins;
}
public Map<String, Object> getPluginConfig(String pluginId) {
public Map<String, Object> getPluginConfig(@NotNull String pluginId) {
return getPluginConfig(pluginId, false);
}
public Map<String, Object> getPluginConfig(String pluginId, boolean create) {
public Map<String, Object> getPluginConfig(@NotNull String pluginId, boolean create) {
Object config = plugins.get(pluginId);
if (config instanceof Map) {
return (Map<String, Object>) config;
@@ -155,8 +158,21 @@ public class CBAppConfig {
}
}
public <T> T getPluginOption(String pluginId, String option) {
public <T> T getPluginOption(@NotNull String pluginId, @NotNull String option) {
return (T)getPluginConfig(pluginId).get(option);
}
public Map<String, Object> getAuthConfiguration() {
return authConfiguration;
}
@NotNull
public Map<String, Object> getAuthConfiguration(@NotNull String providerId) {
Object apConfig = authConfiguration.get(providerId);
if (apConfig instanceof Map) {
return (Map<String, Object>) apConfig;
}
return Collections.emptyMap();
}
}
@@ -74,6 +74,7 @@ public class CBApplication extends BaseApplicationImpl {
return (CBApplication) BaseApplicationImpl.getInstance();
}
private String serverURL;
private int serverPort = CBConstants.DEFAULT_SERVER_PORT;
private String serverName = null;
private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT;
@@ -103,6 +104,10 @@ public class CBApplication extends BaseApplicationImpl {
public CBApplication() {
}
public String getServerURL() {
return serverURL;
}
public int getServerPort() {
return serverPort;
}
@@ -174,7 +179,7 @@ public class CBApplication extends BaseApplicationImpl {
try {
loadConfiguration(configPath);
File runtimeConfigFile = getRuntimeConfigFile();
File runtimeConfigFile = getRuntimeAppConfigFile();
if (runtimeConfigFile.exists()) {
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
parseConfiguration(runtimeConfigFile);
@@ -274,6 +279,11 @@ public class CBApplication extends BaseApplicationImpl {
});
Runtime.getRuntime().addShutdownHook(shutdownThread);
if (configurationMode) {
// Try to configure automatically
performAutoConfiguration(new File(configPath).getParentFile());
}
try {
initializeServer();
} catch (DBException e) {
@@ -287,6 +297,55 @@ public class CBApplication extends BaseApplicationImpl {
return null;
}
/**
* Configures server automatically.
* Called on startup
* @param configPath
*/
protected void performAutoConfiguration(File configPath) {
String autoServerName = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_NAME);
String autoServerURL = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_URL);
String autoAdminName = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
String autoAdminPassword = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
if (CommonUtils.isEmpty(autoServerName) || CommonUtils.isEmpty(autoAdminName) || CommonUtils.isEmpty(autoAdminPassword)) {
// Try to load from auto config file
if (configPath.exists()) {
File autoConfigFile = new File(configPath, CBConstants.AUTO_CONFIG_FILE_NAME);
if (autoConfigFile.exists()) {
Properties autoProps = new Properties();
try (InputStream is = new FileInputStream(autoConfigFile)) {
autoProps.load(is);
autoServerName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_NAME);
autoServerURL = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_URL);
autoAdminName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
autoAdminPassword = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
} catch (IOException e) {
log.error("Error loading auto configuration file '" + autoConfigFile.getAbsolutePath() + "'", e);
}
}
}
}
if (CommonUtils.isEmpty(autoServerName) || CommonUtils.isEmpty(autoAdminName) || CommonUtils.isEmpty(autoAdminPassword)) {
log.info("No auto configuration was found. Server must be configured manually");
return;
}
try {
finishConfiguration(
autoServerName,
autoServerURL,
autoAdminName,
autoAdminPassword,
Collections.emptyList(),
maxSessionIdleTime,
getAppConfiguration());
} catch (Exception e) {
log.error("Error loading server auto configuration", e);
}
}
protected void initializeServer() throws DBException {
}
@@ -327,8 +386,13 @@ public class CBApplication extends BaseApplicationImpl {
}
@NotNull
private File getRuntimeConfigFile() {
return new File(getDataDirectory(false), CBConstants.RUNTIME_CONFIG_FILE_NAME);
private File getRuntimeAppConfigFile() {
return new File(getDataDirectory(false), CBConstants.RUNTIME_APP_CONFIG_FILE_NAME);
}
@NotNull
private File getRuntimeProductConfigFile() {
return new File(getDataDirectory(false), CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME);
}
@NotNull
@@ -398,6 +462,12 @@ public class CBApplication extends BaseApplicationImpl {
Map<String, Object> serverConfig = JSONUtils.getObject(configProps, "server");
serverPort = JSONUtils.getInteger(serverConfig, CBConstants.PARAM_SERVER_PORT, serverPort);
if (serverConfig.containsKey(CBConstants.PARAM_SERVER_URL)) {
serverURL = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_URL, serverURL);
} else if (serverURL == null) {
serverURL = "http://" + InetAddress.getLocalHost().getHostName() + ":" + serverPort;
}
serverName = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_NAME, serverName);
contentRoot = getRelativePath(
JSONUtils.getString(serverConfig, CBConstants.PARAM_CONTENT_ROOT, contentRoot), homeFolder);
@@ -439,6 +509,19 @@ public class CBApplication extends BaseApplicationImpl {
}
}
}
// Add product config from runtime
{
File rtConfig = getRuntimeProductConfigFile();
if (rtConfig.exists()) {
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
} catch (Exception e) {
log.error("Error reading product runtime configuration", e);
}
}
}
}
static String getRelativePath(String path, String curDir) {
@@ -504,6 +587,7 @@ public class CBApplication extends BaseApplicationImpl {
public synchronized void finishConfiguration(
@NotNull String newServerName,
@NotNull String newServerURL,
@NotNull String adminName,
@Nullable String adminPassword,
@NotNull List<WebAuthInfo> authInfoList,
@@ -520,7 +604,7 @@ public class CBApplication extends BaseApplicationImpl {
// Save runtime configuration
log.debug("Saving runtime configuration");
saveRuntimeConfig(newServerName, sessionExpireTime, appConfig);
saveRuntimeConfig(newServerName, newServerURL, sessionExpireTime, appConfig);
// Grant permissions to predefined connections
if (isConfigurationMode() && appConfig.isAnonymousAccessEnabled()) {
@@ -530,7 +614,7 @@ public class CBApplication extends BaseApplicationImpl {
// Re-load runtime configuration
try {
log.debug("Reloading application configuration");
File runtimeConfigFile = getRuntimeConfigFile();
File runtimeConfigFile = getRuntimeAppConfigFile();
if (runtimeConfigFile.exists()) {
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
parseConfiguration(runtimeConfigFile);
@@ -543,7 +627,7 @@ public class CBApplication extends BaseApplicationImpl {
}
public synchronized void flushConfiguration() throws DBException {
saveRuntimeConfig(serverName, maxSessionIdleTime, appConfiguration);
saveRuntimeConfig(serverName, serverURL, maxSessionIdleTime, appConfiguration);
}
@@ -565,9 +649,9 @@ public class CBApplication extends BaseApplicationImpl {
}
}
private void saveRuntimeConfig(String newServerName, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
private void saveRuntimeConfig(String newServerName, String newServerURL, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
File runtimeConfigFile = getRuntimeConfigFile();
File runtimeConfigFile = getRuntimeAppConfigFile();
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder()
.setLenient()
@@ -582,6 +666,9 @@ public class CBApplication extends BaseApplicationImpl {
if (!CommonUtils.isEmpty(newServerName)) {
JSONUtils.field(json, CBConstants.PARAM_SERVER_NAME, newServerName);
}
if (!CommonUtils.isEmpty(newServerURL)) {
JSONUtils.field(json, CBConstants.PARAM_SERVER_URL, newServerURL);
}
if (sessionExpireTime > 0) {
JSONUtils.field(json, CBConstants.PARAM_SESSION_EXPIRE_PERIOD, sessionExpireTime);
}
@@ -619,12 +706,15 @@ public class CBApplication extends BaseApplicationImpl {
json.endObject();
}
if (appConfig.getEnabledAuthProviders() != null) {
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()), true);
}
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins());
}
if (!CommonUtils.isEmpty(appConfig.getAuthConfiguration())) {
JSONUtils.serializeProperties(json, "authConfiguration", appConfig.getAuthConfiguration());
}
json.endObject();
}
@@ -30,13 +30,16 @@ public class CBConstants {
public static final String DEFAULT_CONFIG_FILE_PATH = "/etc/cloudbeaver.conf";
public static final String RUNTIME_DATA_DIR_NAME = ".data";
public static final String RUNTIME_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf";
public static final String RUNTIME_APP_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf";
public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf";
public static final String AUTO_CONFIG_FILE_NAME = ".cloudbeaver.auto.conf";
public static final String CLI_PARAM_WEB_CONFIG = "-web-config";
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_NAME = "serverName";
public static final String PARAM_CONTENT_ROOT = "contentRoot";
public static final String PARAM_SERVER_URL = "serverURL";
public static final String PARAM_ROOT_URI = "rootURI";
public static final String PARAM_SERVICES_URI = "serviceURI";
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
@@ -66,9 +69,15 @@ public class CBConstants {
public static final String DEFAUL_APP_ANONYMOUS_ROLE_NAME = "user";
public static final String DEFAULT_SERVER_ADDR = "localhost";
public static final String VAR_CB_LOCAL_HOST_ADDR = "CB_LOCAL_HOST_ADDR";
public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal";
public static final String VAR_AUTO_CB_SERVER_NAME = "CB_SERVER_NAME";
public static final String VAR_AUTO_CB_SERVER_URL = "CB_SERVER_URL";
public static final String VAR_AUTO_CB_ADMIN_NAME = "CB_ADMIN_NAME";
public static final String VAR_AUTO_CB_ADMIN_PASSWORD = "CB_ADMIN_PASSWORD";
}
@@ -463,6 +463,28 @@ class CBSecurityController implements DBWSecurityController {
}
}
@Override
public void updateRole(WebRole role) throws DBCException {
if (!isSubjectExists(role.getRoleId())) {
throw new DBCException("Role '" + role.getRoleId() + "' doesn't exists");
}
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
createAuthSubject(dbCon, role.getRoleId(), SUBJECT_ROLE);
try (PreparedStatement dbStat = dbCon.prepareStatement(
"UPDATE CB_ROLE SET ROLE_NAME=?,ROLE_DESCRIPTION=? WHERE ROLE_ID=?")) {
dbStat.setString(1, CommonUtils.notEmpty(role.getName()));
dbStat.setString(2, CommonUtils.notEmpty(role.getDescription()));
dbStat.setString(3, role.getRoleId());
dbStat.execute();
}
txn.commit();
}
} catch (SQLException e) {
throw new DBCException("Error updating role info in database", e);
}
}
@Override
public void deleteRole(String roleId) throws DBCException {
try (Connection dbCon = database.openConnection()) {
@@ -319,6 +319,15 @@ public class GraphQLEndpoint extends HttpServlet {
return request;
}
public static HttpServletResponse getServletResponse(DataFetchingEnvironment env) {
GraphQLContext context = env.getContext();
HttpServletResponse response = context.get("response");
if (response == null) {
throw new IllegalStateException("Null response");
}
return response;
}
public static GraphQLBindingContext getBindingContext(DataFetchingEnvironment env) {
GraphQLContext context = env.getContext();
return context.get("bindingContext");
@@ -45,8 +45,9 @@ public class CBJettyServer {
// Handler configuration
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setResourceBase(application.getContentRoot());
servletContextHandler.setContextPath(application.getRootURI());
servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), application.getRootURI());
String rootURI = application.getRootURI();
servletContextHandler.setContextPath(rootURI);
servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), "/*");
servletContextHandler.addServlet(new ServletHolder("images", new CBImageServlet()), application.getServicesURI() + "images/*");
servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), application.getServicesURI() + "gql/*");
servletContextHandler.addEventListener(new CBServerContextListener());
@@ -1,5 +1,7 @@
package io.cloudbeaver.server.jetty;
import io.cloudbeaver.server.CBApplication;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.ResourceService;
@@ -9,7 +11,10 @@ import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
@WebServlet(urlPatterns = "/")
public class CBStaticServlet extends DefaultServlet {
@@ -26,10 +31,33 @@ public class CBStaticServlet extends DefaultServlet {
}
private static ResourceService makeResourceService() {
ResourceService resourceService = new ResourceService();
ResourceService resourceService = new ProxyResourceService();
resourceService.setCacheControl(new HttpField(HttpHeader.CACHE_CONTROL, "public, max-age=" + STATIC_CACHE_SECONDS));
return resourceService;
}
private static class ProxyResourceService extends ResourceService {
@Override
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, HttpContent content, Enumeration<String> reqRanges) throws IOException {
if (content.getResource().getName().endsWith("index.html")) {
return patchIndexHtml(response, content);
}
return super.sendData(request, response, include, content, reqRanges);
}
private boolean patchIndexHtml(HttpServletResponse response, HttpContent content) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
content.getResource().writeTo(baos, 0, content.getContentLengthValue());
String indexContents = new String(baos.toByteArray(), StandardCharsets.UTF_8);
indexContents = indexContents.replace("{ROOT_URI}", CBApplication.getInstance().getRootURI());
byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
putHeaders(response, content, indexBytes.length);
response.getOutputStream().write(indexBytes);
return true;
}
}
}
@@ -31,6 +31,7 @@ import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -92,18 +93,22 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
return GraphQLEndpoint.getServletRequest(env);
}
protected static HttpServletResponse getServletResponse(DataFetchingEnvironment env) {
return GraphQLEndpoint.getServletResponse(env);
}
protected static DBWBindingContext getBindingContext(DataFetchingEnvironment env) {
return GraphQLEndpoint.getBindingContext(env);
}
protected static WebSession getWebSession(DataFetchingEnvironment env) throws DBWebException {
return CBPlatform.getInstance().getSessionManager().getWebSession(
getServletRequest(env));
getServletRequest(env), getServletResponse(env));
}
protected static WebSession getWebSession(DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
return CBPlatform.getInstance().getSessionManager().getWebSession(
getServletRequest(env), errorOnNotFound);
getServletRequest(env), getServletResponse(env), errorOnNotFound);
}
@NotNull
@@ -26,6 +26,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@@ -75,13 +76,13 @@ public interface DBWServiceCore extends DBWService {
boolean closeSession(HttpServletRequest request) throws DBWebException;
@WebAction(requirePermissions = {})
boolean touchSession(HttpServletRequest request) throws DBWebException;
boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse servletResponse) throws DBWebException;
@WebAction(requirePermissions = {})
boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException;
boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException;
@WebAction
boolean changeSessionLanguage(WebSession webSession, String locale) throws DBWebException;
boolean changeSessionLanguage(@NotNull WebSession webSession, String locale) throws DBWebException;
///////////////////////////////////////////
// Connections
@@ -89,13 +90,6 @@ public interface DBWServiceCore extends DBWService {
@WebAction
WebConnectionInfo getConnectionState(WebSession webSession, String connectionId) throws DBWebException;
// Replaced with initConnection
@Deprecated
@WebAction
WebConnectionInfo openConnection(
@NotNull WebSession webSession,
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
@WebAction
WebConnectionInfo initConnection(
@NotNull WebSession webSession,
@@ -79,13 +79,15 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
;
model.getMutationType()
.dataFetcher("openSession", env -> getService(env).openSession(sessionManager.getWebSession(GraphQLEndpoint.getServletRequest(env), false)))
.dataFetcher("openSession", env -> getService(env).openSession(
sessionManager.getWebSession(GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env), false)))
.dataFetcher("closeSession", env -> getService(env).closeSession(GraphQLEndpoint.getServletRequest(env)))
.dataFetcher("touchSession", env -> getService(env).touchSession(GraphQLEndpoint.getServletRequest(env)))
.dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(GraphQLEndpoint.getServletRequest(env)))
.dataFetcher("touchSession", env -> getService(env).touchSession(
GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env)))
.dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(
GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env)))
.dataFetcher("changeSessionLanguage", env -> getService(env).changeSessionLanguage(getWebSession(env), env.getArgument("locale")))
.dataFetcher("openConnection", env -> getService(env).openConnection(getWebSession(env), getConnectionConfig(env)))
.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")))
@@ -33,8 +33,6 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNModel;
@@ -54,6 +52,7 @@ import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -76,7 +75,7 @@ public class WebServiceCore implements DBWServiceCore {
public List<WebDatabaseDriverConfig> getDriverList(@NotNull WebSession webSession, String driverId) {
List<WebDatabaseDriverConfig> result = new ArrayList<>();
for (DBPDriver driver : CBPlatform.getInstance().getApplicableDrivers()) {
if (driverId == null || driverId.equals(WebServiceUtils.makeDriverFullId(driver))) {
if (driverId == null || driverId.equals(driver.getFullId())) {
result.add(new WebDatabaseDriverConfig(webSession, driver));
}
}
@@ -173,13 +172,13 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public boolean touchSession(HttpServletRequest request) throws DBWebException {
return CBPlatform.getInstance().getSessionManager().touchSession(request);
public boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
return CBPlatform.getInstance().getSessionManager().touchSession(request, response);
}
@Override
public boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException {
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request);
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request, response);
if (session == null) {
return false;
} else {
@@ -190,7 +189,7 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public boolean changeSessionLanguage(WebSession webSession, String locale) {
public boolean changeSessionLanguage(@NotNull WebSession webSession, String locale) {
webSession.setLocale(locale);
return true;
}
@@ -200,51 +199,6 @@ public class WebServiceCore implements DBWServiceCore {
return webSession.getWebConnectionInfo(connectionId);
}
@Deprecated
@Override
public WebConnectionInfo openConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig config) throws DBWebException {
String templateId = config.getTemplateId();
if (CommonUtils.isEmpty(templateId)) {
throw new DBWebException("Only preconfigured data sources are supported yet");
}
DBPDataSourceRegistry templateRegistry = WebServiceUtils.getGlobalDataSourceRegistry();
DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId);
if (dataSourceTemplate == null) {
throw new DBWebException("Datasource '" + templateId + "' not found");
}
DBPDataSourceRegistry sessionRegistry = webSession.getSingletonProject().getDataSourceRegistry();
DBPDataSourceContainer newDataSource = sessionRegistry.createDataSource(dataSourceTemplate);
newDataSource.setSavePassword(true);
((DataSourceDescriptor)newDataSource).setTemporary(true);
DBPConnectionConfiguration cfg = newDataSource.getConnectionConfiguration();
if (AuthModelDatabaseNative.ID.equals(config.getAuthModelId())) {
cfg.setUserName(JSONUtils.getString(config.getCredentials(), "userName"));
cfg.setUserPassword(JSONUtils.getString(config.getCredentials(), "userPassword"));
} else {
cfg.setUserName(config.getUserName());
cfg.setUserPassword(config.getUserPassword());
}
if (!CommonUtils.isEmpty(config.getName())) {
newDataSource.setName(config.getName());
}
if (!CommonUtils.isEmpty(config.getDescription())) {
newDataSource.setDescription(config.getDescription());
}
try {
newDataSource.connect(webSession.getProgressMonitor(), true, false);
} catch (DBException e) {
throw new DBWebException("Error connecting to database", e);
}
sessionRegistry.addDataSource(newDataSource);
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource);
webSession.addConnection(connectionInfo);
return connectionInfo;
}
@Override
public WebConnectionInfo initConnection(
@NotNull WebSession webSession,
@@ -321,9 +275,11 @@ public class WebServiceCore implements DBWServiceCore {
@Override
public WebConnectionInfo updateConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig config) throws DBWebException {
if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
throw new DBWebException("Connection edit is restricted by server configuration");
}
// Do not check for custom connection option. Already created connections can be edited.
// Also template connections can be edited
// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
// throw new DBWebException("Connection edit is restricted by server configuration");
// }
DBPDataSourceRegistry sessionRegistry = webSession.getSingletonProject().getDataSourceRegistry();
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(config.getConnectionId());
@@ -345,8 +301,9 @@ public class WebServiceCore implements DBWServiceCore {
@Override
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");
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(connectionId);
if (connectionInfo.getDataSourceContainer().getProject() != webSession.getSingletonProject()) {
throw new DBWebException("Global connection '" + connectionInfo.getName() + "' configuration cannot be deleted");
}
closeAndDeleteConnection(webSession, connectionId, true);
return true;
@@ -431,7 +388,7 @@ public class WebServiceCore implements DBWServiceCore {
} else {
testDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, sessionRegistry);
}
webSession.provideAuthParameters(testDataSource, testDataSource.getConnectionConfiguration());
webSession.provideAuthParameters(webSession.getProgressMonitor(), testDataSource, testDataSource.getConnectionConfiguration());
testDataSource.setSavePassword(true); // We need for test to avoid password callback
try {
ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> {});
@@ -132,14 +132,7 @@ public class WebDatabaseObjectInfo {
@Property
public String[] getFeatures() {
List<String> features = new ArrayList<>();
if (object instanceof DBPScriptObject) features.add("script");
if (object instanceof DBPScriptObjectExt) features.add("scriptExtended");
if (object instanceof DBSDataContainer) features.add("dataContainer");
if (object instanceof DBSDataManipulator) features.add("dataManipulator");
if (object instanceof DBSEntity) features.add("entity");
if (object instanceof DBSSchema) features.add("schema");
if (object instanceof DBSCatalog) features.add("catalog");
if (object instanceof DBSObjectContainer) features.add("objectContainer");
getObjectFeatures(object, features);
if (object instanceof DBPDataSourceContainer) {
features.add("dataSource");
DBPDataSourceContainer dbpDataSourceContainer = (DBPDataSourceContainer) this.object;
@@ -149,10 +142,37 @@ public class WebDatabaseObjectInfo {
if (dbpDataSourceContainer.isTemporary()) {
features.add("dataSourceTemporary");
}
if (dbpDataSourceContainer.isConnected()) {
DBPDataSource dataSource = dbpDataSourceContainer.getDataSource();
if (dataSource != null) {
getObjectFeatures(dataSource, features);
}
}
}
return features.toArray(new String[0]);
}
private static void getObjectFeatures(DBSObject object, List<String> features) {
if (object instanceof DBPScriptObject) features.add("script");
if (object instanceof DBPScriptObjectExt) features.add("scriptExtended");
if (object instanceof DBSDataContainer) features.add("dataContainer");
if (object instanceof DBSDataManipulator) features.add("dataManipulator");
if (object instanceof DBSEntity) features.add("entity");
if (object instanceof DBSSchema) features.add("schema");
if (object instanceof DBSCatalog) features.add("catalog");
if (object instanceof DBSObjectContainer) {
features.add("objectContainer");
try {
Class<? extends DBSObject> childType = ((DBSObjectContainer) object).getPrimaryChildType(null);
if (DBSEntity.class.isAssignableFrom(childType)) {
features.add("entityContainer");
}
} catch (Exception e) {
log.error(e);
}
}
}
@Property
public String[] getEditors() {
return null;
@@ -118,6 +118,15 @@ public class WebNavigatorNodeInfo {
if (node instanceof DBNContainer) {
features.add("container");
}
boolean isShared = false;
if (node instanceof DBNDatabaseNode) {
isShared = !((DBNDatabaseNode) node).getDataSourceContainer().isManageable();
} else if (node instanceof DBNLocalFolder) {
//isShared = ((DBNLocalFolder) node).getDataSourceRegistry().is
}
if (isShared) {
features.add("shared");
}
return features.toArray(new String[0]);
}
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import java.util.List;
import java.util.Map;
@@ -33,11 +34,25 @@ import java.util.Map;
*/
public interface DBWServiceSQL extends DBWService {
@WebAction
WebSQLContextInfo[] listContexts(@NotNull WebSession session, @Nullable String connectionId, @Nullable String contextId) throws DBWebException;
@WebAction
WebSQLDialectInfo getDialectInfo(@NotNull WebSQLProcessor processor) throws DBWebException;
@WebAction
WebSQLCompletionProposal[] getCompletionProposals(@NotNull WebSQLContextInfo sqlContext, @NotNull String query, Integer position, Integer maxResults) throws DBWebException;
WebSQLCompletionProposal[] getCompletionProposals(
@NotNull WebSQLContextInfo sqlContext,
@NotNull String query,
Integer position,
Integer maxResults,
Boolean simpleMode) throws DBWebException;
@WebAction
DBCLogicalOperator[] getSupportedOperations(
@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
int attributeIndex) throws DBWebException;
@WebAction
WebSQLContextInfo createContext(@NotNull WebSQLProcessor processor, String defaultCatalog, String defaultSchema) throws DBWebException;
@@ -82,4 +97,14 @@ public interface DBWServiceSQL extends DBWService {
@WebAction
WebSQLExecuteInfo asyncGetQueryResults(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
@WebAction
WebAsyncTaskInfo asyncSqlExplainExecutionPlan(
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> options) throws DBException;
@WebAction
WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
}
@@ -0,0 +1,35 @@
/*
* 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.sql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.data.DBDValue;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import java.util.Map;
/**
* Web value serializer
*/
public interface DBWValueSerializer<VALUE_TYPE extends DBDValue> {
@NotNull
VALUE_TYPE deserializeValue(@NotNull DBCSession session, DBSTypedObject attribute, @NotNull Map<String, Object> webValue) throws DBCException;
}
@@ -0,0 +1,39 @@
/*
* 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.sql;
/**
* Web SQL constants.
*/
public class WebSQLConstants {
public static final int BINARY_PREVIEW_LENGTH = 255;
public static final int BINARY_MAX_LENGTH = 1 * 1024 * 1024;
public static final String VALUE_TYPE_ATTR = "$type";
public static final String VALUE_TYPE_COLLECTION = "collection";
public static final String VALUE_TYPE_MAP = "map";
public static final String VALUE_TYPE_DOCUMENT = "document";
public static final String VALUE_TYPE_CONTENT = "content";
public static final String VALUE_TYPE_GEOMETRY = "geometry";
public static final String ATTR_TEXT = "text";
public static final String ATTR_BINARY = "binary";
public static final String ATTR_DATA = "data";
}
@@ -150,6 +150,10 @@ public class WebSQLContextInfo {
return id;
}
public String getConnectionId() {
return processor.getConnection().getId();
}
public String getDefaultCatalog() {
return defaultCatalog == null ? null : defaultCatalog.getName();
}
@@ -16,9 +16,14 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityAttribute;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
@@ -38,7 +43,7 @@ public class WebSQLDataFilter {
private int offset;
private int limit ;
private String where;
private List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
private final List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
public WebSQLDataFilter() {
this.offset = 0;
@@ -85,13 +90,29 @@ public class WebSQLDataFilter {
return constraints;
}
public DBDDataFilter makeDataFilter() {
public String getWhere() {
return where;
}
public DBDDataFilter makeDataFilter(DBRProgressMonitor monitor, DBSDataContainer dataContainer) throws DBException {
DBDDataFilter dataFilter = new DBDDataFilter();
dataFilter.setWhere(where);
if (!CommonUtils.isEmpty(constraints)) {
List<DBDAttributeConstraint> dbdConstraints = new ArrayList<>();
for (WebSQLDataFilterConstraint webConstr : constraints) {
DBDAttributeConstraint dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
DBDAttributeConstraint dbConstraint;
if (dataContainer instanceof DBSEntity) {
DBSEntityAttribute attribute = ((DBSEntity) dataContainer).getAttribute(monitor, webConstr.getAttribute());
if (attribute == null) {
dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
} else {
dbConstraint = new DBDAttributeConstraint(attribute, -1);
}
} else {
dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
}
dbConstraint.setPlainNameReference(true);
if (webConstr.getOrderPosition() != null) {
dbConstraint.setOrderPosition(webConstr.getOrderPosition());
}
@@ -18,8 +18,10 @@ package io.cloudbeaver.service.sql;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.Pair;
/**
@@ -81,4 +83,7 @@ public class WebSQLDialectInfo {
return SQLUtils.getDefaultScriptDelimiter(dialect);
}
public boolean isSupportsExplainExecutionPlan() {
return GeneralUtils.adapt(dataSource, DBCQueryPlanner.class) != null;
}
}
@@ -28,6 +28,7 @@ public class WebSQLExecuteInfo {
private String statusMessage;
private long duration;
private String filterText;
private WebSQLQueryResults[] results;
@Property
@@ -48,6 +49,15 @@ public class WebSQLExecuteInfo {
this.duration = duration;
}
@Property
public String getFilterText() {
return filterText;
}
public void setFilterText(String filterText) {
this.filterText = filterText;
}
@Property
public WebSQLQueryResults[] getResults() {
return results;
@@ -0,0 +1,71 @@
/*
* 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.sql;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.exec.plan.DBCPlan;
import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode;
import java.util.*;
/**
* WebSQLExecutionPlan.
*/
public class WebSQLExecutionPlan {
private static final Log log = Log.getLog(WebSQLExecutionPlan.class);
private final WebSession webSession;
private final DBCPlan plan;
public WebSQLExecutionPlan(WebSession webSession, DBCPlan plan) {
this.webSession = webSession;
this.plan = plan;
}
public String getQuery() {
return plan.getQueryString();
}
public WebSQLExecutionPlanNode[] getNodes() {
Map<String, Object> options = new LinkedHashMap<>();
Map<DBCPlanNode, String> idMap = new IdentityHashMap<>();
List<WebSQLExecutionPlanNode> result = new ArrayList<>();
for (DBCPlanNode node : plan.getPlanNodes(options)) {
addNodeWithNested(result, null, node, idMap);
}
return result.toArray(new WebSQLExecutionPlanNode[0]);
}
private void addNodeWithNested(List<WebSQLExecutionPlanNode> result, DBCPlanNode parentNode, DBCPlanNode node, Map<DBCPlanNode, String> idMap) {
String parentId = idMap.get(parentNode);
String nodeId = String.valueOf(idMap.size());
idMap.put(node, nodeId);
result.add(new WebSQLExecutionPlanNode(webSession, node, nodeId, parentId));
Collection<? extends DBCPlanNode> nested = node.getNested();
if (nested != null) {
for (DBCPlanNode nn : nested) {
addNodeWithNested(result, node, nn, idMap);
}
}
}
}
@@ -0,0 +1,85 @@
/*
* 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.sql;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode;
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
import java.util.Arrays;
/**
* WebSQLExecutionPlanNode.
*/
public class WebSQLExecutionPlanNode {
private static final Log log = Log.getLog(WebSQLExecutionPlanNode.class);
private final WebSession webSession;
private final DBCPlanNode node;
private final String id;
private final String parentId;
public WebSQLExecutionPlanNode(WebSession webSession, DBCPlanNode node, String id, String parentId) {
this.webSession = webSession;
this.node = node;
this.id = id;
this.parentId = parentId;
}
public String getId() {
return id;
}
public String getParentId() {
return parentId;
}
public String getKind() {
return node.getNodeKind().name();
}
public String getName() {
return node.getNodeName();
}
public String getType() {
return node.getNodeType();
}
public String getCondition() {
return node.getNodeCondition();
}
public String getDescription() {
return node.getNodeDescription();
}
@NotNull
public WebPropertyInfo[] getProperties() {
PropertyCollector propertyCollector = new PropertyCollector(node, false);
propertyCollector.collectProperties();
return Arrays.stream(propertyCollector.getProperties())
.filter(p -> !(p instanceof ObjectPropertyDescriptor && ((ObjectPropertyDescriptor) p).isHidden()))
.map(p -> new WebPropertyInfo(webSession, p, propertyCollector)).toArray(WebPropertyInfo[]::new);
}
}
@@ -25,20 +25,23 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDDocument;
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.exec.plan.DBCPlan;
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner;
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlannerConfiguration;
import org.jkiss.dbeaver.model.impl.AbstractExecutionSource;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseItem;
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.SQLUtils;
import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
@@ -96,7 +99,7 @@ public class WebSQLProcessor {
return ruleManager;
}
DBCExecutionContext getExecutionContext() {
public DBCExecutionContext getExecutionContext() {
return DBUtils.getDefaultContext(connection.getDataSource(), false);
}
@@ -153,22 +156,22 @@ public class WebSQLProcessor {
DBCExecutionContext context = getExecutionContext(dataContainer);
{
DBDDataFilter dataFilter = filter.makeDataFilter();
if (dataFilter.hasFilters()) {
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
monitor,
context.getDataSource(),
sql,
dataFilter);
}
}
final WebSQLDataFilter dataFilter = filter;
final String sqlQueryText = sql;
SQLQuery sqlQuery = new SQLQuery(context.getDataSource(), sqlQueryText);
try {
{
DBDDataFilter dataFilter = filter.makeDataFilter(monitor, dataContainer);
if (dataFilter.hasFilters()) {
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
monitor,
context.getDataSource(),
sql,
dataFilter);
}
}
final WebSQLDataFilter dataFilter = filter;
final String sqlQueryText = sql;
SQLQuery sqlQuery = new SQLQuery(context.getDataSource(), sqlQueryText);
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = context.openSession(monitor, DBCExecutionPurpose.USER, "Execute SQL")) {
AbstractExecutionSource source = new AbstractExecutionSource(
@@ -215,7 +218,7 @@ public class WebSQLProcessor {
WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo();
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
DBDDataFilter dataFilter = filter.makeDataFilter();
DBDDataFilter dataFilter = filter.makeDataFilter(monitor, dataContainer);
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Read data from container")) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
@@ -231,10 +234,14 @@ public class WebSQLProcessor {
executeInfo.setDuration(statistics.getTotalTime());
WebSQLQueryResults results = new WebSQLQueryResults(webSession, dataFormat);
results.setResultSet(dataReceiver.getResultSet());
WebSQLQueryResultSet resultSet = dataReceiver.getResultSet();
results.setResultSet(resultSet);
executeInfo.setResults(new WebSQLQueryResults[]{results});
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, filter);
executeInfo.setStatusMessage(dataReceiver.getResultSet().getRows().length + " row(s) fetched");
if (resultSet != null && resultSet.getRows() != null) {
executeInfo.setStatusMessage(resultSet.getRows().length + " row(s) fetched");
}
} catch (DBException e) {
throw new InvocationTargetException(e);
}
@@ -379,7 +386,12 @@ public class WebSQLProcessor {
for (int i = 0; i < updateAttributes.length; i++) {
DBDAttributeBinding updateAttribute = updateAttributes[i];
Object cellRawValue = updateValues.get(String.valueOf(updateAttribute.getOrdinalPosition()));
Object realCellValue = updateAttribute.getValueHandler().getValueFromObject(session, updateAttribute, cellRawValue, false, true);
cellRawValue = WebSQLUtils.makePlainCellValue(session, updateAttribute, cellRawValue);
Object realCellValue = cellRawValue;
// In some cases we already have final value here
if (!(realCellValue instanceof DBDValue)) {
realCellValue = updateAttribute.getValueHandler().getValueFromObject(session, updateAttribute, cellRawValue, false, true);
}
rowValues[i] = realCellValue;
finalRow[updateAttribute.getOrdinalPosition()] = WebSQLUtils.makeWebCellValue(webSession, null, realCellValue, dataFormat);
}
@@ -387,19 +399,30 @@ public class WebSQLProcessor {
DBDAttributeBinding keyAttribute = keyAttributes[i];
if (keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator) {
// Document reference
DBDDocument document = null;
Map<String, Object> keyMap = new LinkedHashMap<>();
DBDAttributeBinding[] attributes = resultsInfo.getAttributes();
for (int j = 0; j < attributes.length; j++) {
DBDAttributeBinding attr = attributes[j];
keyMap.put(attr.getName(), row.getData().get(j));
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData().get(j));
if (plainValue instanceof DBDDocument) {
// FIXME: Hack for DynamoDB. We pass entire document as a key
// FIXME: Let's just return it back for now
document = (DBDDocument) plainValue;
break;
}
keyMap.put(attr.getName(), plainValue);
}
DBDDocument document = ((DBSDocumentLocator) dataContainer).findDocument(session.getProgressMonitor(), keyMap);
if (document == null) {
throw new DBCException("Error finding document by key " + keyMap);
document = ((DBSDocumentLocator) dataContainer).findDocument(session.getProgressMonitor(), keyMap);
if (document == null) {
throw new DBCException("Error finding document by key " + keyMap);
}
}
rowValues[updateAttributes.length + i] = document;
} else {
Object cellValueRaw = row.getData().get(keyAttribute.getOrdinalPosition());
cellValueRaw = WebSQLUtils.makePlainCellValue(session, keyAttribute, cellValueRaw);
rowValues[updateAttributes.length + i] = keyAttribute.getValueHandler().getValueFromObject(session, keyAttribute, cellValueRaw, false, true);
}
}
@@ -440,6 +463,51 @@ public class WebSQLProcessor {
return result;
}
////////////////////////////////////////////////
// ExecutionPlan
@NotNull
public WebSQLExecutionPlan explainExecutionPlan(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration) throws DBWebException {
DBCQueryPlanner planner;
DBCExecutionContext executionContext = getExecutionContext();
if (executionContext != null) {
DBPDataSource dataSource = executionContext.getDataSource();
planner = GeneralUtils.adapt(dataSource, DBCQueryPlanner.class);
} else {
throw new DBWebException("Not connected to data source");
}
if (planner == null) {
throw new DBWebException("Datasource '" + executionContext.getDataSource() + "' doesn't support execution plan");
}
DBCPlan[] dbcPlan = new DBCPlan[1];
try {
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Execute SQL")) {
DBCQueryPlannerConfiguration planConfig = new DBCQueryPlannerConfiguration();
planConfig.getParameters().putAll(configuration);
dbcPlan[0] = planner.planQueryExecution(session, sql, planConfig);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
});
} catch (DBException e) {
throw new DBWebException("Error explaining execution plan", e);
}
return new WebSQLExecutionPlan(webSession, dbcPlan[0]);
}
////////////////////////////////////////////////
// Misc
private void checkRowIdentifier(WebSQLResultsInfo resultsInfo, DBDRowIdentifier rowIdentifier) throws DBWebException {
if (rowIdentifier == null || !rowIdentifier.isValidIdentifier()) {
throw new DBWebException("Can't detect row identifier for data container '" + resultsInfo.getDataContainer().getName() + "'. It must have at least one unique key.");
@@ -470,7 +538,8 @@ public class WebSQLProcessor {
private void fillQueryResults(
@NotNull WebSQLContextInfo contextInfo, @NotNull DBSDataContainer dataContainer,
@NotNull WebSQLContextInfo contextInfo,
@NotNull DBSDataContainer dataContainer,
@NotNull DBCStatement dbStat,
boolean hasResultSet,
@NotNull WebSQLExecuteInfo executeInfo,
@@ -490,7 +559,7 @@ public class WebSQLProcessor {
results.setResultSet(dataReceiver.getResultSet());
}
} else {
int updateRowCount = dbStat.getUpdateRowCount();
long updateRowCount = dbStat.getUpdateRowCount();
if (updateRowCount >= 0) {
results.setUpdateRowCount(updateRowCount);
} else {
@@ -502,6 +571,21 @@ public class WebSQLProcessor {
}
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, filter);
}
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull WebSQLDataFilter filter) throws DBException {
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
StringBuilder where = new StringBuilder();
SQLUtils.appendConditionString(
filter.makeDataFilter(webSession.getProgressMonitor(), dataContainer),
dataSource,
null,
where,
true);
executeInfo.setFilterText(where.toString());
}
}
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
@@ -40,9 +40,9 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
private static final Log log = Log.getLog(WebSQLQueryDataContainer.class);
private DBPDataSource dataSource;
private String query;
private DBSDataContainer queryDataContainer;
private final DBPDataSource dataSource;
private final String query;
private final DBSDataContainer queryDataContainer;
public WebSQLQueryDataContainer(DBPDataSource dataSource, String query) {
this.dataSource = dataSource;
@@ -21,6 +21,7 @@ import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBValueFormatting;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
import org.jkiss.dbeaver.model.meta.Property;
@@ -99,6 +100,11 @@ public class WebSQLQueryResultColumn {
return attrMeta.getPrecision();
}
@Property
public boolean isRequired() {
return attrMeta.isRequired();
}
@Property
public boolean isReadOnly() {
return DBExecUtils.isAttributeReadOnly(attrMeta);
@@ -109,6 +115,11 @@ public class WebSQLQueryResultColumn {
return DBExecUtils.getAttributeReadOnlyStatus(attrMeta);
}
@Property
public DBCLogicalOperator[] getSupportedOperations() {
return attrMeta.getValueHandler().getSupportedOperators(attrMeta);
}
@Override
public String toString() {
return attrMeta.getName();
@@ -17,11 +17,13 @@
package io.cloudbeaver.service.sql;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.gis.DBGeometry;
import org.jkiss.dbeaver.model.gis.GisConstants;
import org.jkiss.dbeaver.model.gis.GisTransformUtils;
@@ -29,9 +31,11 @@ import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.jkiss.dbeaver.utils.ContentUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.Base64;
import org.jkiss.utils.CommonUtils;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
@@ -42,11 +46,18 @@ public class WebSQLUtils {
private static final Log log = Log.getLog(WebSQLUtils.class);
public static final int BINARY_PREVIEW_LENGTH = 255;
public static Object makeWebCellValue(WebSession session, DBSTypedObject type, Object cellValue, WebDataFormat dataFormat) throws DBCException {
if (cellValue instanceof Date) {
return CBConstants.ISO_DATE_FORMAT.format(cellValue);
} else if (cellValue instanceof Number) {
if (cellValue instanceof Double) {
return CommonUtils.niceFormatDouble((Double) cellValue);
} else if (cellValue instanceof Float) {
return CommonUtils.niceFormatDouble((Float) cellValue);
} else if (cellValue instanceof BigDecimal) {
return ((BigDecimal) cellValue).toPlainString();
}
return cellValue.toString();
}
if (cellValue instanceof DBDValue) {
DBDValue dbValue = (DBDValue) cellValue;
@@ -75,7 +86,7 @@ public class WebSQLUtils {
items[i] = makeWebCellValue(session, collection.getComponentType(), collection.getItem(i), dataFormat);
}
Map<String, Object> map = createMapOfType("collection");
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_COLLECTION);
map.put("value", items);
return map;
} else if (value instanceof DBDComposite) {
@@ -85,7 +96,7 @@ public class WebSQLUtils {
struct.put(attr.getName(), makeWebCellValue(session, attr, composite.getAttributeValue(attr), dataFormat));
}
Map<String, Object> map = createMapOfType("map");
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_MAP);
map.put("value", struct);
return map;
}
@@ -95,7 +106,7 @@ public class WebSQLUtils {
@NotNull
private static Map<String, Object> createMapOfType(String type) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("$type", type);
map.put(WebSQLConstants.VALUE_TYPE_ATTR, type);
return map;
}
@@ -109,30 +120,37 @@ public class WebSQLUtils {
throw new DBCException("Error serializing document", e);
}
Map<String, Object> map = createMapOfType("document");
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_DOCUMENT);
map.put("id", CommonUtils.toString(document.getDocumentId()));
map.put("contentType", document.getDocumentContentType());
map.put("properties", Collections.emptyMap());
map.put("data", documentData);
map.put(WebSQLConstants.ATTR_DATA, documentData);
return map;
}
private static Object serializeContentValue(WebSession session, DBDContent value) throws DBCException {
Map<String, Object> map = createMapOfType("content");
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_CONTENT);
if (ContentUtils.isTextContent(value)) {
String stringValue = ContentUtils.getContentStringValue(session.getProgressMonitor(), value);
map.put("text", stringValue);
map.put(WebSQLConstants.ATTR_TEXT, stringValue);
} else {
map.put("binary", true);
map.put(WebSQLConstants.ATTR_BINARY, true);
byte[] binaryValue = ContentUtils.getContentBinaryValue(session.getProgressMonitor(), value);
if (binaryValue != null) {
if (binaryValue.length > BINARY_PREVIEW_LENGTH) {
binaryValue = Arrays.copyOf(binaryValue, BINARY_PREVIEW_LENGTH);
byte[] previewValue = binaryValue;
if (previewValue.length > WebSQLConstants.BINARY_PREVIEW_LENGTH) {
previewValue = Arrays.copyOf(previewValue, WebSQLConstants.BINARY_PREVIEW_LENGTH);
}
map.put("text", GeneralUtils.convertToString(binaryValue, 0, binaryValue.length));
map.put(WebSQLConstants.ATTR_TEXT, GeneralUtils.convertToString(previewValue, 0, previewValue.length));
byte[] inlineValue = binaryValue;
if (inlineValue.length > WebSQLConstants.BINARY_MAX_LENGTH) {
inlineValue = Arrays.copyOf(inlineValue, WebSQLConstants.BINARY_PREVIEW_LENGTH);
}
map.put(WebSQLConstants.ATTR_BINARY, Base64.encode(inlineValue));
} else {
map.put("text", null);
map.put(WebSQLConstants.ATTR_TEXT, null);
}
}
map.put("contentType", value.getContentType());
@@ -141,9 +159,9 @@ public class WebSQLUtils {
}
private static Object serializeGeometryValue(DBGeometry value) {
Map<String, Object> map = createMapOfType("geometry");
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_GEOMETRY);
map.put("srid", value.getSRID());
map.put("text", value.toString());
map.put(WebSQLConstants.ATTR_TEXT, value.toString());
map.put("properties", value.getProperties());
DBGeometry xValue = GisTransformUtils.transformToSRID(value, GisConstants.SRID_4326);
@@ -153,4 +171,29 @@ public class WebSQLUtils {
return map;
}
public static Object makePlainCellValue(DBCSession session, DBSTypedObject attribute, Object value) throws DBCException {
if (value instanceof Map) {
Map<String, Object> map = (Map<String, Object>) value;
Object typeAttr = map.get(WebSQLConstants.VALUE_TYPE_ATTR);
if (typeAttr instanceof String) {
switch ((String)typeAttr) {
case WebSQLConstants.VALUE_TYPE_CONTENT: {
if (map.get(WebSQLConstants.ATTR_BINARY) != null) {
throw new DBCException("Binary content edit is not supported yet");
}
value = map.get(WebSQLConstants.ATTR_TEXT);
break;
}
default: {
DBWValueSerializer<?> valueSerializer = WebServiceRegistry.getInstance().createValueSerializer((String) typeAttr);
if (valueSerializer == null) {
throw new DBCException("Value type '" + typeAttr + "' edit is not supported yet");
}
value = valueSerializer.deserializeValue(session, attribute, map);
}
}
}
}
return value;
}
}
@@ -28,6 +28,7 @@ import io.cloudbeaver.service.sql.impl.WebServiceSQL;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.utils.CommonUtils;
@@ -51,14 +52,27 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
.dataFetcher("sqlDialectInfo", env ->
getService(env).getDialectInfo(getSQLProcessor(env))
)
.dataFetcher("sqlListContexts", env ->
getService(env).listContexts(getWebSession(env),
env.getArgument("connectionId"),
env.getArgument("contextId"))
)
.dataFetcher("sqlCompletionProposals", env ->
getService(env).getCompletionProposals(
getSQLContext(env),
env.getArgument("query"),
env.getArgument("position"),
env.getArgument("maxResults")
env.getArgument("maxResults"),
env.getArgument("simpleMode")
)
);
)
.dataFetcher("sqlSupportedOperations", env ->
getService(env).getSupportedOperations(
getSQLContext(env),
env.getArgument("resultsId"),
env.getArgument("attributeIndex"))
)
;
model.getMutationType()
.dataFetcher("sqlContextCreate", env -> getService(env).createContext(
@@ -111,6 +125,16 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
.dataFetcher("asyncSqlExecuteResults", env ->
getService(env).asyncGetQueryResults(
getWebSession(env), env.getArgument("taskId")
))
.dataFetcher("asyncSqlExplainExecutionPlan", env ->
getService(env).asyncSqlExplainExecutionPlan(
getSQLContext(env),
env.getArgument("query"),
env.getArgument("configuration")
))
.dataFetcher("asyncSqlExplainExecutionPlanResult", env ->
getService(env).asyncSqlExplainExecutionPlanResult(
getWebSession(env), env.getArgument("taskId")
));
}
@@ -136,6 +160,11 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
return getSQLConfiguration(connectionInfo.getSession()).getSQLProcessor(connectionInfo);
}
@Nullable
public static WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo, boolean connect) throws DBWebException {
return getSQLConfiguration(connectionInfo.getSession()).getSQLProcessor(connectionInfo, connect);
}
@NotNull
public static WebSQLContextInfo getSQLContext(DataFetchingEnvironment env) throws DBWebException {
WebSQLProcessor processor = getSQLProcessor(env);
@@ -163,7 +192,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
private final Map<WebConnectionInfo, WebSQLProcessor> processors = new HashMap<>();
WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo) throws DBWebException {
return WebServiceBindingSQL.getSQLProcessor(connectionInfo, true);
}
WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo, boolean connect) throws DBWebException {
if (connectionInfo.getDataSource() == null) {
if (!connect) {
return null;
}
try {
connectionInfo.getDataSourceContainer().connect(connectionInfo.getSession().getProgressMonitor(), true, false);
} catch (DBException e) {
@@ -20,15 +20,19 @@ package io.cloudbeaver.service.sql.impl;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebAsyncTaskProcessor;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.sql.*;
import org.eclipse.jface.text.Document;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.SQLQuery;
@@ -37,9 +41,14 @@ import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionAnalyzer;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionRequest;
import org.jkiss.dbeaver.model.sql.parser.SQLParserContext;
import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -48,6 +57,30 @@ import java.util.Map;
*/
public class WebServiceSQL implements DBWServiceSQL {
@Override
public WebSQLContextInfo[] listContexts(@NotNull WebSession session, @Nullable String connectionId, @Nullable String contextId) throws DBWebException {
List<WebConnectionInfo> conToRead = new ArrayList<>();
if (connectionId != null) {
WebConnectionInfo webConnection = WebServiceBindingBase.getWebConnection(session, connectionId);
conToRead.add(webConnection);
} else {
conToRead.addAll(session.getConnections());
}
List<WebSQLContextInfo> contexts = new ArrayList<>();
for (WebConnectionInfo con : conToRead) {
WebSQLProcessor sqlProcessor = WebServiceBindingSQL.getSQLProcessor(con, false);
if (sqlProcessor != null) {
WebSQLContextInfo[] conContexts = sqlProcessor.getContexts();
contexts.addAll(Arrays.asList(conContexts));
}
}
if (contextId != null) {
contexts.removeIf(c -> !c.getId().equals(contextId));
}
return contexts.toArray(new WebSQLContextInfo[0]);
}
@Override
@NotNull
public WebSQLDialectInfo getDialectInfo(@NotNull WebSQLProcessor processor) throws DBWebException {
@@ -57,19 +90,43 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@NotNull
public WebSQLCompletionProposal[] getCompletionProposals(@NotNull WebSQLContextInfo sqlContext, @NotNull String query, Integer position, Integer maxResults) throws DBWebException {
public WebSQLCompletionProposal[] getCompletionProposals(
@NotNull WebSQLContextInfo sqlContext,
@NotNull String query,
Integer position,
Integer maxResults,
Boolean simpleMode) throws DBWebException
{
try {
DBPDataSource dataSource = sqlContext.getProcessor().getConnection().getDataSourceContainer().getDataSource();
Document document = new Document();
document.set(query);
SQLScriptElement activeQuery = new SQLQuery(dataSource, query);
WebSQLCompletionContext completionContext = new WebSQLCompletionContext(sqlContext);
SQLScriptElement activeQuery;
if (position != null) {
SQLParserContext parserContext = new SQLParserContext(
() -> sqlContext.getProcessor().getExecutionContext(),
completionContext.getSyntaxManager(),
completionContext.getRuleManager(),
document);
activeQuery = SQLScriptParser.extractActiveQuery(parserContext, position, 0);
} else {
activeQuery = new SQLQuery(dataSource, query);
}
SQLCompletionRequest request = new SQLCompletionRequest(
new WebSQLCompletionContext(sqlContext),
completionContext,
document,
position == null ? 0 : position,
activeQuery,
false
CommonUtils.getBoolean(simpleMode, false)
);
SQLCompletionAnalyzer analyzer = new SQLCompletionAnalyzer(request);
analyzer.runAnalyzer(sqlContext.getProcessor().getWebSession().getProgressMonitor());
List<SQLCompletionProposalBase> proposals = analyzer.getProposals();
@@ -88,6 +145,16 @@ public class WebServiceSQL implements DBWServiceSQL {
}
}
@Override
public DBCLogicalOperator[] getSupportedOperations(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, int attributeIndex) throws DBWebException {
WebSQLResultsInfo results = contextInfo.getResults(resultsId);
if (attributeIndex < 0 || attributeIndex >= results.getAttributes().length) {
throw new DBWebException("Invalid attribute index (" + attributeIndex + ")");
}
DBDAttributeBinding attribute = results.getAttributes()[attributeIndex];
return attribute.getValueHandler().getSupportedOperators(attribute);
}
@Override
public WebSQLContextInfo createContext(@NotNull WebSQLProcessor processor, String defaultCatalog, String defaultSchema) throws DBWebException {
try {
@@ -199,5 +266,36 @@ public class WebServiceSQL implements DBWServiceSQL {
return null;
}
////////////////////////////////////////////////////
// Explain plan
@Override
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(@NotNull WebSQLContextInfo contextInfo, @NotNull String sql, @NotNull Map<String, Object> configuration) throws DBException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Explain execution plan", 1);
monitor.subTask("Explain query [" + sql + "] execution plan");
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, contextInfo, sql, configuration);
this.result = "Execution plan explain has been scheduled";
this.extendedResults = executeResults;
} catch (Throwable e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
return contextInfo.getProcessor().getWebSession().createAndRunAsyncTask("SQL query execution plan explain", runnable);
}
@Override
public WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
if (taskStatus != null) {
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
}
return null;
}
}
@@ -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.21.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.29.qualifier
Bundle-Release-Date: 20210802
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.21-SNAPSHOT</version>
<version>1.0.29-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -26,12 +26,15 @@ type AdminUserInfo {
grantedRoles: [ID!]!
grantedConnections: [AdminConnectionGrantInfo!]!
origin: ObjectOrigin!
origins: [ObjectOrigin!]!
linkedAuthProviders: [String!]!
}
type AdminRoleInfo {
roleId: ID!
roleName: String
description: String
rolePermissions: [ID]!
}
@@ -47,6 +50,7 @@ type AdminPermissionInfo {
input ServerConfigInput {
serverName: String
serverURL: String
adminName: String
adminPassword: String
@@ -73,7 +77,8 @@ extend type Query {
createUser(userId: ID!): AdminUserInfo!
deleteUser(userId: ID!): Boolean
createRole(roleId: ID!): AdminRoleInfo!
createRole(roleId: ID!, roleName: String, description: String): AdminRoleInfo!
updateRole(roleId: ID!, roleName: String, description: String): AdminRoleInfo!
deleteRole(roleId: ID!): Boolean
grantUserRole(userId: ID!, roleId: ID!): Boolean
@@ -40,6 +40,10 @@ public class AdminRoleInfo {
return role.getName();
}
public String getDescription() {
return role.getDescription();
}
public List<String> getRolePermissions() {
return rolePermissions;
}
@@ -30,20 +30,22 @@ import java.util.Map;
public class AdminServerConfig {
private String serverName;
private String serverURL;
private String adminName;
private String adminPassword;
private boolean anonymousAccessEnabled;
private boolean customConnectionsEnabled;
private boolean publicCredentialsSaveEnabled;
private boolean adminCredentialsSaveEnabled;
private final boolean anonymousAccessEnabled;
private final boolean customConnectionsEnabled;
private final boolean publicCredentialsSaveEnabled;
private final boolean adminCredentialsSaveEnabled;
private final List<String> enabledAuthProviders;
private long sessionExpireTime;
public AdminServerConfig(Map<String, Object> params) {
this.serverName = JSONUtils.getString(params, "serverName");
this.serverURL = JSONUtils.getString(params, "serverURL");
this.adminName = JSONUtils.getString(params, "adminName");
this.adminPassword = JSONUtils.getString(params, "adminPassword");
@@ -69,6 +71,14 @@ public class AdminServerConfig {
this.serverName = serverName;
}
public String getServerURL() {
return serverURL;
}
public void setServerURL(String serverURL) {
this.serverURL = serverURL;
}
public String getAdminName() {
return adminName;
}
@@ -16,21 +16,22 @@
*/
package io.cloudbeaver.service.admin;
import io.cloudbeaver.DBWAuthProviderExternal;
import io.cloudbeaver.DBWConnectionGrant;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebRole;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.model.user.WebUserOriginInfo;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
@@ -42,6 +43,8 @@ public class AdminUserInfo {
private final WebSession session;
private final WebUser user;
private String[] userLinkedProviders;
public AdminUserInfo(WebSession session, WebUser user) {
this.session = session;
@@ -78,16 +81,34 @@ public class AdminUserInfo {
}
@Property
public WebUserOriginInfo getOrigin() {
String providerId = user.getMetaParameter(DBWAuthProviderExternal.META_AUTH_PROVIDER);
if (CommonUtils.isEmpty(providerId)) {
providerId = LocalAuthProvider.PROVIDER_ID;
public WebUserOriginInfo[] getOrigins() throws DBWebException {
List<WebUserOriginInfo> result = new ArrayList<>();
for (String provider : getUserLinkedProviders()) {
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(provider);
if (authProvider == null) {
log.error("Auth provider '" + provider + "' not found");
} else {
result.add(new WebUserOriginInfo(session, user, authProvider));
}
}
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(providerId);
if (authProvider == null) {
log.error("Auth provider '" + providerId + "' not found");
return result.toArray(new WebUserOriginInfo[0]);
}
@Property
public String[] getLinkedAuthProviders() throws DBWebException {
return getUserLinkedProviders();
}
private String[] getUserLinkedProviders() throws DBWebException {
if (userLinkedProviders != null) {
return userLinkedProviders;
}
return new WebUserOriginInfo(session, user, authProvider);
try {
userLinkedProviders = CBApplication.getInstance().getSecurityController().getUserLinkedProviders(user.getUserId());
} catch (DBCException e) {
throw new DBWebException("Error reading user linked providers", e);
}
return userLinkedProviders;
}
}
@@ -53,7 +53,11 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId) throws DBWebException;
AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
AdminRoleInfo updateRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteRole(@NotNull WebSession webSession, String roleId) throws DBWebException;
@@ -53,7 +53,17 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
.dataFetcher("deleteUser",
env -> getService(env).deleteUser(getWebSession(env), env.getArgument("userId")))
.dataFetcher("createRole",
env -> getService(env).createRole(getWebSession(env), env.getArgument("roleId")))
env -> getService(env).createRole(
getWebSession(env),
env.getArgument("roleId"),
env.getArgument("roleName"),
env.getArgument("description")))
.dataFetcher("updateRole",
env -> getService(env).createRole(
getWebSession(env),
env.getArgument("roleId"),
env.getArgument("roleName"),
env.getArgument("description")))
.dataFetcher("deleteRole",
env -> getService(env).deleteRole(getWebSession(env), env.getArgument("roleId")))
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service.admin.impl;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.admin.AdminConnectionSearchInfo;
@@ -135,7 +134,7 @@ public class ConnectionSearcher implements DBRRunnableWithProgress {
}
AdminConnectionSearchInfo searchInfo = portCache.get(driverPort);
String driverId = WebServiceUtils.makeDriverFullId(driver);
String driverId = driver.getFullId();
if (searchInfo != null) {
searchInfo.getPossibleDrivers().add(driverId);
return;
@@ -137,12 +137,14 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@NotNull
@Override
public AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId) throws DBWebException {
public AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException {
if (roleId.isEmpty()) {
throw new DBWebException("Empty role ID");
}
try {
WebRole newRole = new WebRole(roleId);
newRole.setName(roleName);
newRole.setDescription(description);
CBPlatform.getInstance().getApplication().getSecurityController().createRole(newRole);
return new AdminRoleInfo(newRole);
} catch (Exception e) {
@@ -150,6 +152,23 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
}
@NotNull
@Override
public AdminRoleInfo updateRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException {
if (roleId.isEmpty()) {
throw new DBWebException("Empty role ID");
}
try {
WebRole newRole = new WebRole(roleId);
newRole.setName(roleName);
newRole.setDescription(description);
CBPlatform.getInstance().getApplication().getSecurityController().updateRole(newRole);
return new AdminRoleInfo(newRole);
} catch (Exception e) {
throw new DBWebException("Error updating role " + roleId, e);
}
}
@Override
public boolean deleteRole(@NotNull WebSession webSession, String roleId) throws DBWebException {
try {
@@ -396,8 +415,11 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
}
boolean configurationMode = CBApplication.getInstance().isConfigurationMode();
CBApplication.getInstance().finishConfiguration(
config.getServerName(),
config.getServerURL(),
adminName,
adminPassword,
authInfoList,
@@ -405,7 +427,13 @@ public class WebServiceAdmin implements DBWServiceAdmin {
appConfig);
// Refresh active session
webSession.forceUserRefresh(null);
if (configurationMode) {
// In config mode we always refresh because admin user doesn't exist yet
webSession.forceUserRefresh(null);
} else {
// Just reload session state
webSession.forceUserRefresh(webSession.getUser());
}
} catch (Throwable e) {
throw new DBWebException("Error configuring server", e);
}
@@ -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.21.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.29.qualifier
Bundle-Release-Date: 20210802
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.21-SNAPSHOT</version>
<version>1.0.29-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -13,6 +13,9 @@ type AuthCredentialInfo {
admin: Boolean
# This field must be shown in login form
user: Boolean
# This field can be used as user identifier
identifying: Boolean
possibleValues: [String]
encryption: AuthCredentialEncryption
}
@@ -52,7 +55,7 @@ type UserAuthToken {
type UserInfo {
# User unique identifier
userId: String!
userId: ID!
# Human readable display name. It is taken from the first auth provider which was used for user login.
displayName: String
@@ -75,4 +78,6 @@ extend type Query {
authProviders: [AuthProviderInfo!]!
authChangeLocalPassword(oldPassword: String!, newPassword: String!): Boolean!
}
@@ -48,4 +48,6 @@ public interface DBWServiceAuth extends DBWService {
@WebAction(requirePermissions = {})
WebAuthProviderInfo[] getAuthProviders();
@WebAction()
boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException;
}
@@ -47,6 +47,11 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
})
.dataFetcher("activeUser", env -> getService(env).activeUser(getWebSession(env, false)))
.dataFetcher("authProviders", env -> getService(env).getAuthProviders())
.dataFetcher("authChangeLocalPassword", env -> getService(env).changeLocalPassword(
getWebSession(env),
env.getArgument("oldPassword"),
env.getArgument("newPassword")
))
;
}
@@ -17,6 +17,7 @@
package io.cloudbeaver.service.auth.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebAuthProviderInfo;
@@ -74,7 +75,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
} else {
if (!providerEnabled) {
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
// Admin can use local provider anytime
if (!isAdminAuthTry(providerId, authParameters)) {
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
}
}
}
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(providerId);
@@ -137,11 +141,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
userId);
}
}
} else {
// We may need to associate new credentials with active user
if (linkWithActiveUser) {
securityController.setUserCredentials(userId, authProvider, userCredentials);
}
}
// We may need to associate new credentials with active user
if (linkWithActiveUser) {
securityController.setUserCredentials(userId, authProvider, userCredentials);
}
}
@@ -149,7 +152,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
throw new DBCException("Invalid user credentials");
}
}
if (curUser != null && !curUser.getUserId().equals(userId)) {
if (linkWithActiveUser && 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");
}
@@ -205,6 +208,23 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
}
private boolean isAdminAuthTry(@NotNull String providerId, @NotNull Map<String, Object> authParameters) {
boolean isAdmin = false;
if (LocalAuthProvider.PROVIDER_ID.equals(providerId)) {
Object userId = authParameters.get(LocalAuthProvider.CRED_USER);
if (userId != null) {
try {
isAdmin = CBPlatform.getInstance().getApplication().getSecurityController()
.getUserPermissions(CommonUtils.toString(userId))
.contains(DBWConstants.PERMISSION_ADMIN);
} catch (DBCException e) {
log.error(e);
}
}
}
return isAdmin;
}
@Override
public void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException {
if (webSession.getUser() == null) {
@@ -228,4 +248,16 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
.toArray(WebAuthProviderInfo[]::new);
}
@Override
public boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException {
if (webSession.getUser() == null) {
throw new DBWebException("User must be logged in");
}
try {
return LocalAuthProvider.changeUserPassword(webSession, oldPassword, newPassword);
} catch (DBException e) {
throw new DBWebException("Error changing user password", e);
}
}
}
@@ -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.22.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.30.qualifier
Bundle-Release-Date: 20210802
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.22-SNAPSHOT</version>
<version>1.0.30-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -207,7 +207,9 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
exporter,
properties);
DatabaseTransferProducer producer = new DatabaseTransferProducer(dataContainer, parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter());
DatabaseTransferProducer producer = new DatabaseTransferProducer(
dataContainer,
parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter(monitor, dataContainer));
DatabaseProducerSettings producerSettings = new DatabaseProducerSettings();
producerSettings.setExtractType(DatabaseProducerSettings.ExtractType.SINGLE_QUERY);
producerSettings.setQueryRowCount(false);
@@ -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.25.qualifier
Bundle-Release-Date: 20210405
Bundle-Version: 1.0.33.qualifier
Bundle-Release-Date: 20210802
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.25-SNAPSHOT</version>
<version>1.0.33-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
+10 -3
View File
@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>drivers.clickhouse</artifactId>
<version>1.0.0</version>
@@ -18,7 +18,14 @@
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.2.6</version>
<classifier>shaded</classifier>
<version>0.3.1-patch</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
+2 -2
View File
@@ -18,12 +18,12 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
<version>42.2.20</version>
</dependency>
<dependency>
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>2.2.1</version>
<version>2.5.0</version>
</dependency>
</dependencies>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.45.qualifier"
version="1.0.53.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.45-SNAPSHOT</version>
<version>1.0.53-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="20.0.6.qualifier"
version="21.1.1.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.6-SNAPSHOT</version>
<version>21.1.1-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.sample.feature"
label="%featureName"
version="1.0.25.qualifier"
version="1.0.33.qualifier"
provider-name="%providerName">
<description>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.sample.feature</artifactId>
<version>1.0.25-SNAPSHOT</version>
<version>1.0.33-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="20.0.6.qualifier"
version="21.1.1.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>20.0.6-SNAPSHOT</version>
<version>21.1.1-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<dbeaver-product>CloudBeaver CE</dbeaver-product>
<dbeaver-version>20.0.6</dbeaver-version>
<dbeaver-version>21.1.1</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.6.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="21.1.1.qualifier" useFeatures="true" includeLaunchers="false">
<configIni use="default">
</configIni>
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>20.0.6-SNAPSHOT</version>
<version>21.1.1-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.6.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="21.1.1.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>20.0.6-SNAPSHOT</version>
<version>21.1.1-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
+19 -14
View File
@@ -6,15 +6,18 @@
"packages": [
"packages/*"
],
"nohoist": ["**/core-cli", "**/core-cli/**"]
"nohoist": [
"**/core-cli",
"**/core-cli/**"
]
},
"scripts": {
"prebuild": "npm run clean-dist",
"clean-dist": "rimraf lib/*"
},
"devDependencies": {
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"@types/react": "^16.9.12",
"@types/react-dom": "^16.9.12",
"@typescript-eslint/eslint-plugin": "^4.13.0",
"@typescript-eslint/parser": "^4.13.0",
"eslint": "^7.18.0",
@@ -33,19 +36,21 @@
"eslint-plugin-standard": "^5.0.0",
"eslint-webpack-plugin": "^2.4.1",
"lerna": "^3.22.1",
"mobx": "^6.0.4",
"mobx-react-lite": "^3.1.6",
"mobx": "^6.3.0",
"mobx-react-lite": "^3.2.0",
"node-sass": "^5.0.0",
"preact": "^10.5.10",
"preact-render-to-string": "^5.1.12",
"preact-ssr-prepass": "^1.1.3",
"path-browserify": "^1.0.1",
"preact": "^10.5.13",
"preact-jsx-runtime": "^1.2.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"reakit": "^1.3.4",
"preact-render-to-string": "^5.1.19",
"preact-ssr-prepass": "^1.1.3",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"reakit": "^1.3.8",
"reflect-metadata": "^0.1.13",
"reshadow": "^0.0.1-alpha.78",
"typescript": "^4.2.3"
"reshadow": "^0.0.1-alpha.80",
"rimraf": "^3.0.2",
"typescript": "^4.2.4"
},
"dependencies": {}
}
}
@@ -1,21 +0,0 @@
/*
* 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.
*/
import styled from 'reshadow';
interface Props {
className?: string;
}
export const AdministrationTools: React.FC<Props> = function AdministrationTools({ children, className }) {
return styled()(
<administration-tools as='div' className={className}>
{children}
</administration-tools>
);
};
@@ -23,6 +23,7 @@ interface IActivationData {
screen: IAdministrationItemRoute;
configurationWizard: boolean;
outside: boolean;
outsideAdminPage: boolean;
}
@injectable()
@@ -161,17 +162,19 @@ export class AdministrationItemService {
async activate(
screen: IAdministrationItemRoute,
configurationWizard: boolean,
outside: boolean
outside: boolean,
outsideAdminPage: boolean
): Promise<void> {
await this.activationTask.execute({ screen, configurationWizard, outside });
await this.activationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
}
async deActivate(
screen: IAdministrationItemRoute,
configurationWizard: boolean,
outside: boolean
outside: boolean,
outsideAdminPage: boolean
): Promise<void> {
await this.deActivationTask.execute({ screen, configurationWizard, outside });
await this.deActivationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
}
async canActivate(
@@ -198,7 +201,22 @@ export class AdministrationItemService {
return true;
}
private activateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
private activateHandler: IExecutorHandler<IActivationData> = async ({
screen,
configurationWizard,
outside,
outsideAdminPage,
}) => {
let lastItem = 0;
while (true) {
const items = this.getActiveItems(configurationWizard);
if (lastItem === items.length) {
break;
}
await items[lastItem]?.onLoad?.(configurationWizard, outside, outsideAdminPage);
lastItem++;
}
if (configurationWizard) {
let item = 0;
while (true) {
@@ -216,20 +234,25 @@ export class AdministrationItemService {
return;
}
await item.onActivate?.(configurationWizard, outside);
await item.onActivate?.(configurationWizard, outside, outsideAdminPage);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onActivate?.(screen.param, configurationWizard, outside);
}
};
private deActivateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
private deActivateHandler: IExecutorHandler<IActivationData> = async ({
screen,
configurationWizard,
outside,
outsideAdminPage,
}) => {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
await item.onDeActivate?.(configurationWizard, outside);
await item.onDeActivate?.(configurationWizard, outside, outsideAdminPage);
if (screen.sub) {
await this.getItemSub(item, screen.sub)?.onDeActivate?.(screen.param, configurationWizard, outside);
@@ -44,7 +44,11 @@ export type AdministrationItemSubContentProps = AdministrationItemContentProps &
};
export type AdministrationItemSubContentComponent = React.FunctionComponent<AdministrationItemSubContentProps>;
export type AdministrationItemEvent = (configurationWizard: boolean, outside: boolean) => Promise<void> | void;
export type AdministrationItemEvent = (
configurationWizard: boolean,
outside: boolean,
outsideAdminPage: boolean
) => Promise<void> | void;
export type AdministrationItemCanActivateEvent = (
configurationWizard: boolean,
administration: boolean
@@ -91,6 +95,7 @@ export interface IAdministrationItemOptions {
replace?: IAdministrationItemReplaceOptions;
getDrawerComponent: () => AdministrationItemDrawerComponent;
getContentComponent: () => AdministrationItemContentComponent;
onLoad?: AdministrationItemEvent;
onActivate?: AdministrationItemEvent;
onDeActivate?: AdministrationItemEvent;
canActivate?: AdministrationItemCanActivateEvent;
@@ -13,7 +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 { GlobalConstants } from '@cloudbeaver/core-utils';
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute';
@@ -68,15 +68,9 @@ export class AdministrationScreenService {
private autoSaveService: LocalStorageSaveService,
private serverConfigResource: ServerConfigResource
) {
makeObservable(this, {
info: observable,
itemState: observable,
activeScreen: computed,
});
this.info = {
workspaceId: '',
version: BuildVersion.version || '',
version: GlobalConstants.version || '',
serverVersion: '',
configurationMode: false,
};
@@ -84,6 +78,12 @@ export class AdministrationScreenService {
this.activationEvent = new Executor();
this.ensurePermissions = new Executor();
makeObservable(this, {
info: observable,
itemState: observable,
activeScreen: computed,
});
this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE);
this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO);
this.permissionsResource.onDataUpdate.addPostHandler(() => {
@@ -150,18 +150,7 @@ export class AdministrationScreenService {
if (!this.serverConfigResource.isLoaded()) {
throw new Error('Administration screen getItemState can be used only after server configuration loaded');
}
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.workspaceId = this.serverConfigResource.workspaceId;
this.info.configurationMode = this.isConfigurationMode;
this.info.serverVersion = this.serverConfigResource.serverVersion;
this.info.version = BuildVersion.version || '';
}
this.validateState();
if (defaultState) {
if (!this.itemState.has(name) || update) {
@@ -194,11 +183,13 @@ export class AdministrationScreenService {
const toScreen = this.getScreen(nextState);
const screen = this.getScreen(state);
if (screen) {
await this.administrationItemService.deActivate(
screen,
this.isConfigurationMode,
screen.item !== toScreen?.item
screen.item !== toScreen?.item,
toScreen === null
);
}
@@ -239,17 +230,35 @@ export class AdministrationScreenService {
await this.administrationItemService.activate(
screen,
this.isConfigurationMode,
screen.item !== fromScreen?.item
screen.item !== fromScreen?.item,
fromScreen === null
);
}
}
private validateState() {
if (
this.info.workspaceId !== this.serverConfigResource.workspaceId
|| this.info.configurationMode !== this.isConfigurationMode
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|| this.info.version !== GlobalConstants.version
) {
this.clearItemsState();
this.info.workspaceId = this.serverConfigResource.workspaceId;
this.info.configurationMode = this.isConfigurationMode;
this.info.serverVersion = this.serverConfigResource.serverVersion;
this.info.version = GlobalConstants.version || '';
}
}
private async checkPermissions(state: RouterState): Promise<boolean> {
if (!this.isAdministrationRouteActive(state.name)) {
return false;
}
if (!(await this.isAccessProvided(state))) {
const accessProvided = await this.isAccessProvided(state);
if (!accessProvided) {
this.screenService.navigateToRoot();
return false;
}
@@ -269,7 +278,9 @@ export class AdministrationScreenService {
await this.ensurePermissions.execute();
if (!(await this.permissionsService.hasAsync(EAdminPermission.admin))) {
const administrator = await this.permissionsService.hasAsync(EAdminPermission.admin);
if (!administrator) {
return false;
}

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