Compare commits

...
40 Commits
Author SHA1 Message Date
serge-rider 8155d328a2 Revert "CB-4603 Set global default user team ID"
This reverts commit 6118e706e0.
2024-04-03 12:05:00 +02:00
serge-rider 6118e706e0 CB-4603 Set global default user team ID 2024-04-03 10:35:52 +02:00
serge-rider eab07b9e9f CB-4603 Set global default user team ID 2024-04-03 10:28:36 +02:00
serge-rider 449c31bf44 24.0.2 version bump 2024-04-02 20:24:10 +02:00
32b4e4ec19 CB-3330 add data import plugin (#2506)
* CB-3330 add data import plugin

* CB-3330 add mock api

* CB-3330 pass processor id

* CB-3330 remove extra code

* CB-3330 simplify logic, allow import only one file

* CB-3330. Implemented import data to table

* CB-3330 replace mocks with api

* CB-3330 add references

* CB-3330. Parsing request, added xml, xls

* CB-3330 support new settings

* CB-3330 use getter

* CB-3330 add svg icons

* CB-3330 pass arguments in handleImport fn

* CB-3330 remove extra fn wrapper

* CB-3330. Fix build

* CB-3330. Fix build

* CB-3330. Fix import

* CB-3330. Fixed import

* CB-3300 fix: frontend can build

* CB-3330. Refactor after review

* CB-3330. Refactor after review, delete temp file after import

* CB-3330. Refactor after review

* CB-3330. Refactor after review

* CB-3330. Added log message

* CB-3330. Added response for task

* CB-3330 fix icon and button shadow

* CB-3330. Refactor response value

* CB-3330. Refactor response value

* CB-3330. delete unused imports

* CB-3330 run additional task after file import

* CB-3330 always allow data import for admins

* CB-3330 import resource as module

---------

Co-authored-by: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com>
Co-authored-by: denis.sinelnikov <denis.sinelnikov@dbeaver.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-04-02 16:51:27 +00:00
101d26158b CB-4603. Refactor searching by group, include default group for all s… (#2440)
* CB-4603. Refactor searching by group, include default group for all sql query with team

---------

Co-authored-by: DenisSinelnikov <denis.sinelnikov@dbaever.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-04-02 16:50:21 +00:00
Ainurandkseniaguzeeva 8b5aac0093 CB-4829 connection cache same ids fix (#2491)
* CB-4829 connection cache same ids fix

* CB-4829 find connection info with project id fix

* CB-4829 add project id to gql functions

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-04-02 16:47:48 +00:00
Serge RiderandDaria Marutkina 5fd847f209 CB-4924 Fix CVE-2022-45868. Remove web interface from H2 driver (#2526)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-04-02 17:23:37 +02:00
Alexander Skoblikov 2936b14aed CB-4500 preference to enable env variables resolving (#2522)
* CB-4500 preference to enable env variables resolving

* CB-4500 save systemVariablesResolvingEnabled to runtime config
2024-04-02 15:15:15 +00:00
sergeyteleshev 6dfbbecd59 CB-4943: fix: Value Panel has correct layout with new groups styles (#2524) 2024-04-02 15:52:58 +02:00
sergeyteleshevandDaria Marutkina 44b6f8a3ae Cb 4548 sql editor resultsets can be closed one by one only when execute sql statement has been used for several queries (#2503)
* CB-4548 fix: removes all result tabs on "close all" action

* CB-4548 adds "close all of the same query" action for sql result tabs

* Revert "CB-4548 adds "close all of the same query" action for sql result tabs"

This reverts commit 453664e41c.

* CB-4548 adds close all of the same group

* CB-4548 adds handleCloseTabGroup with SqlResultTabService

* CB-4548 sql editor bootstrap cleanup

* СB-4548 group tabs logic moved logic to sql-editor-plugin

* СB-4548 cleanup

* CB-4548 adds ACTION_TAB_CLOSE_SQL_RESULT_GROUP to the end of the tabs

* CB-4548 closes group tabs via tabs context close function (to save highlight tab logic)

* CB-4548 pr fixes

* CB-4548 code cleanup

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-04-02 11:55:42 +02:00
AlexeyandEvgenia Bezborodova 9f46e49e01 CB-4776 fix: input default value handling (#2518)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-04-02 11:44:17 +02:00
Alexander SkoblikovandDaria Marutkina 552163e6ee CB-4836 convert max idle time to seconds (#2514)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-03-29 15:31:42 +00:00
Alexey 517db739ae CB-4938 CB-4937 fix: metadata editor and shortcuts (#2520) 2024-03-29 17:38:18 +03:00
Ainur 8004db0f8b CB-4919 update results fix (#2509) 2024-03-28 17:00:45 +00:00
AinurandEvgenia Bezborodova 66f6ccb245 CB-4569 set default database when getting driver props (#2512)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-03-28 16:59:53 +00:00
77f531ddac Update trino driver to 438 (#2372)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>
2024-03-28 16:58:42 +00:00
Cheng PanandDaria Marutkina 192da2c0d6 Support Apache Kyuubi as built-in datasource (#1336)
* Add Kyuubi driver JDBC driver 1.9.0
---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-03-28 16:58:06 +00:00
sergeyteleshevandEvgenia Bezborodova f3c6cf94d7 CB-4030 makes clear log viewer button sticky to the top-right corner (#2505)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-03-28 17:32:06 +03:00
eaa26dfe52 CB-4619 user scope settings (#2471)
* CB-4619 feat: update gql schema

* CB-4808 bulk update user parameters

* CB-4808 param type fix

* CB-4619 feat: updateUserPreferences API


* CB-4619 refactor: settings api

* CB-4619 refactor: move file

* CB-4619 refactor: settings api

* CB-4619 fix: pr review

* CB-4619 refactor: remove web-based product settings

* CB-4619 fix: after pr review

* CB-4619 refactor: move product conf to cloudbeaver conf

* CB-4619 fix: hide settings bar and log exception with changing session language

* CB-4619 fix: revert after merge

* CB-4808 new settings api

* CB-4808 update product.conf format

* CB-4808 update product.conf format

* CB-4619 feat: support server provided settings

* CB-4619 feat: prettify settings

* CB-4619 chore: map sql editor settings group

* CB-4808 add use short/long object name settings to CloudBeaver

* CB-4808 update default product.conf

* CB-4808 mode short/long settings to SQLModelPreferences

* CB-4808 review fix

* CB-4619 refactor: unit tests

* CB-4619 fix: prevent websocket opening spam

* CB-4619 fix: dependencies

* CB-4619 fix: remove extra theme loading

* CB-4619 fix: rename settings alias resolver file

* CB-4619 fix: after review

* CB-4619 fix conflicts

* CB-4619 rename server property

* CB-4619 remove shortname property

* CB-4619 fix legacy product conf reading

* CB-4619 merge root and runtime configurations

* CB-4619 chore: remove log viewer setting

* CB-4619 integer fields with env variables fix

* CB-4619 fix: property naming

* CB-4619 fix: parseJSONFlat null values

* CB-4619 fix: settings loading

* CB-4619 fix: save only actual settings

---------

Co-authored-by: Aleksandr Skoblikov <aleksandr.skoblikov@dbeaver.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-03-28 10:30:52 +00:00
AnastasiyaandDaria Marutkina b54dd5380d CB-4913 fix contact site link in the Product Information (#2507)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-03-27 14:44:49 +03:00
alexandDaria Marutkina f548406446 CB-4676 change delete button text to cancel (#2499)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-03-27 14:44:34 +03:00
ad37e89ecb CB-4686 add new client event for updating session info (#2462)
* CB-4810 add new client event for updating session info

* CB-4810 update request parameters on creating web session

* CB-4810 deprecate update session gql functions

* CB-4810 fix tests

* CB-4810 add params to session state event

* CB-4810 add params to gql schema

* CB-4686 feat: migrates touch session from REST to WebSockets

* CB-4686 fix: do not open websocket if session is expired

* Revert "CB-4686 fix: do not open websocket if session is expired"

This reverts commit 1f6ba77998.

* CB-4686 setData uses performUpdate for the SessionResource

* CB-4686 unsubscribes from the websocket when session expired event arrived

* Revert "CB-4686 unsubscribes from the websocket when session expired event arrived"

This reverts commit 7aeffffc67.

* CB-4810 rename client event

* CB-4686 pr fixes

* CB-4686 makes onDataOutdated execution manual

* CB-4686 fix: onDataOutdated.execute executes in the end of the performUpdate

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: Alexey <wrouds@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-03-27 10:45:09 +00:00
sergeyteleshevandmr-anton-t a6a01c2896 CB-4910-broken-layout-for-login-identity-providers-form (#2498)
* authorization-federated-auth-providers-has-correct-layout

* Revert "authorization-federated-auth-providers-has-correct-layout"

This reverts commit cfd97b045d.

* fix: configList layout with container

* CB-4910 centers buttons in configuration list

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-27 09:01:29 +03:00
Ainurandmr-anton-t cae7699a48 Cb 4280 refactor cb application configuration logic (#2242)
* CB-4280 prettify reading configuration file

* CB-4280 serialize db config, remove it from te

* CB-4280 extract configs from app classes

* CB-4280 resolve conflicts

* CB-4280 fixes after review

* CB-4280 fix copyrights

* CB-4280 add comments

* CB-4280 resolve conflicts

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-26 15:12:49 +00:00
Daria Marutkina d9150180f9 Merge pull request #2497 from dbeaver/dariamarutkina-patch-1
CB_2401 chore: update README
2024-03-25 17:31:30 +01:00
Daria Marutkina 95b7f35228 CB_2401 chore: update README 2024-03-25 17:31:18 +01:00
alexandmr-anton-t 1fd1ebeecf CB-4074 transform metadata editor footer to the new context menu (#2490)
* CB-4074 transform metadata editor footer to the new context menu

* CB-4074 use existing data context for nodes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-25 16:07:04 +01:00
Serge RiderandEvgenia Bezborodova 4daf1efe49 CB-4903 Clickhouse driver version update (#2494)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-03-25 14:03:05 +01:00
alexandmr-anton-t affbe444a0 CB-4707 move connection shield logic to separate component (#2478)
* CB-4707 move connection shield logic to separate component

* CB-4707 use lazy import

* CB-4707 revert interface

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 20:33:34 +03:00
sergeyteleshevands.teleshev e9163b8a2b cleans up the frontend ts references (#2493)
Co-authored-by: s.teleshev <s.teleshev@mu.se>
2024-03-22 12:38:24 +03:00
alexandmr-anton-t b3f7d85687 CB-4884 migrate sql editor to css modules (#2487)
* CB-4884 migrate sql editor to css modules

* CB-4884 review fixes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 07:37:05 +03:00
e033a63f5c CB-4657 removes reshadow from plugin-connections-administration (#2484)
Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 07:32:32 +03:00
d4a9c6595e CB-4656 removes reshadow from plugin-connections (#2481)
Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 07:32:08 +03:00
7f5fb194c5 CB-4655 removes reshadow from plugin-authentication-administration (#2485)
Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 07:31:45 +03:00
2d44a492fc CB-4686 removes reshadow from core-ui (#2489)
* CB-4686 removes reshadow from core-ui

* CB-4283 pr fixes

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-22 07:30:47 +03:00
ebb517318b CB-4471 removes reshadow from plugin-administration (#2479)
* CB-4471 removes reshadow from plugin-administration

* CB-4471 fill is has higher style priority

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-21 20:34:37 +03:00
23de1131b9 CB-4654 removes reshadow from plugin-authentication (#2480)
* CB-4654 removes reshadow from plugin-authentication

* CB-4654 ConfigurationList uses container component

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-21 20:32:06 +03:00
alexandmr-anton-t a5911223ef CB-4859 migrate some styles from driver properties table and metadata… (#2469)
* CB-4859 migrate some styles from driver properties table and metadata editor

* CB-4859 use useS instead of clsx

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-03-20 14:13:19 +03:00
Alexey 37e4c699fe chore: update vscode configs (#2483) 2024-03-20 13:34:25 +03:00
657 changed files with 9691 additions and 5636 deletions
+1
View File
@@ -40,6 +40,7 @@ server/test/io.cloudbeaver.test.platform/workspace/.data/
.classpath
.settings/
workspace-dev-ce/
deploy/cloudbeaver
server/**/target
apps/**/target
+12 -12
View File
@@ -7,21 +7,19 @@
"name": "Chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/..",
"outFiles": [
"${workspaceFolder}/../cloudbeaver/packages/**/dist/**/*.{js,jsx}"
],
"smartStep": true,
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///*": "${workspaceFolder}/../*"
},
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"${workspaceFolder}/webapp/**/node_modules/**/*.js",
"${workspaceFolder}/webapp/**/dist/**/*.js"
]
"disableNetworkCache": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "java",
"name": "CloudBeaver CE",
"cwd": "${workspaceFolder}/../opt/cbce",
"cwd": "${workspaceFolder}/workspace-dev-ce",
"preLaunchTask": "Generate dev proprties for CBCE",
"request": "launch",
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
"windows": {
@@ -75,8 +73,10 @@
"-arch",
"aarch64",
"-nl",
"en_US",
"-showsplash"
"en",
"-showsplash",
"-web-config",
"conf/cloudbeaver.conf"
],
"vmArgs": [
"-XX:+IgnoreUnrecognizedVMOptions",
+29
View File
@@ -33,6 +33,35 @@
"cwd": "${workspaceFolder}/webapp/packages/product-default"
}
},
{
"label": "Generate dev proprties for CBCE",
"type": "shell",
"command": "mvn",
"osx": {
"args": [
"package",
"-q",
"exec:java",
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/Eclipse.app/Contents/Eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
]
},
"windows": {
"args": [
"package",
"-q",
"exec:java",
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
]
},
"options": {
"cwd": "${workspaceFolder}/../idea-rcp-launch-config-generator"
},
"presentation": {
"reveal": "silent",
"close": true,
"clear": false
}
},
{
"label": "Build CE",
"type": "shell",
+6 -1
View File
@@ -22,7 +22,12 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 24.0.1. 2024-03-25
- Added support for two modes for committing changes to the database:
- Auto-commit transfers all changes that you make immediately to the database;
- Manual commit requires your confirmation before committing a change to the database or rolling it back.
- Different bug fixes and enhancements have been made.
### 24.0.0. 2024-03-04
- Changes since 23.3.0:
-32
View File
@@ -1,32 +0,0 @@
// Product configuration. Customized web application behavior
// It is in JSONC format
{
// Global properties
core: {
// User defaults
user: {
defaultTheme: "light",
defaultLanguage: "en"
},
app: {
// Log viewer config
logViewer: {
refreshTimeout: 3000,
logBatchSize: 1000,
maxLogRecords: 2000,
maxFailedRequests: 3
}
}
},
// Notifications config
core_events: {
notificationsPool: 20
},
plugin_data_spreadsheet_new: {
hidden: false
},
plugin_data_export: {
disabled: false
},
sql.proposals.insert.table.alias: true
}
@@ -11,7 +11,21 @@
rootURI: "/",
serviceURI: "/api/",
productConfiguration: "conf/product.conf",
productSettings: {
# Global properties
core.theming.theme: 'light',
core.localization.localization: 'en',
plugin.sql-editor.autoSave: true,
plugin.sql-editor.disabled: false,
# max size of the file that can be uploaded to the editor (in kilobytes)
plugin.sql-editor.maxFileSize: 10240,
plugin.log-viewer.disabled: false,
plugin.log-viewer.logBatchSize: 1000,
plugin.log-viewer.maxFailedRequests: 3,
plugin.log-viewer.maxLogRecords: 2000,
plugin.log-viewer.refreshTimeout: 3000,
sql.proposals.insert.table.alias: PLAIN
},
expireSessionAfterPeriod: 1800000,
@@ -33,7 +47,7 @@
},
database: {
driver="h2_embedded_v2",
driver: "h2_embedded_v2",
url: "jdbc:h2:${workspace}/.data/cb.h2v2.dat",
initialDataConfiguration: "conf/initial-data.conf",
pool: {
@@ -48,9 +62,11 @@
app: {
anonymousAccessEnabled: true,
anonymousUserRole: "user",
defaultUserTeam: "user",
grantConnectionsAccessToAnonymousTeam: false,
supportsCustomConnections: false,
showReadOnlyConnectionInfo: false,
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
forwardProxy: false,
@@ -9,7 +9,21 @@
rootURI: "/",
serviceURI: "/api/",
productConfiguration: "conf/product.conf",
productSettings: {
# Global properties
core.theming.theme: 'light',
core.localization.localization: 'en',
plugin.sql-editor.autoSave: true,
plugin.sql-editor.disabled: false,
# max size of the file that can be uploaded to the editor (in kilobytes)
plugin.sql-editor.maxFileSize: 10240,
plugin.log-viewer.disabled: false,
plugin.log-viewer.logBatchSize: 1000,
plugin.log-viewer.maxFailedRequests: 3,
plugin.log-viewer.maxLogRecords: 2000,
plugin.log-viewer.refreshTimeout: 3000,
sql.proposals.insert.table.alias: PLAIN
},
expireSessionAfterPeriod: 1800000,
@@ -18,7 +32,7 @@
enableSecurityManager: false,
database: {
driver="h2_embedded_v2",
driver:"h2_embedded_v2",
url: "jdbc:h2:${workspace}/.data/cb.h2v2.dat",
initialDataConfiguration: "conf/initial-data.conf",
pool: {
@@ -48,6 +62,7 @@
grantConnectionsAccessToAnonymousTeam: false,
supportsCustomConnections: false,
showReadOnlyConnectionInfo: false,
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
forwardProxy: false,
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.49.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.49-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -18,6 +18,7 @@ package io.cloudbeaver.model;
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.*;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
@@ -27,6 +28,7 @@ import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.meta.PropertyLength;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
import org.jkiss.utils.CommonUtils;
@@ -242,4 +244,12 @@ public class WebPropertyInfo {
return CommonUtils.toString(value);
}
@Nullable
@Property
public List<String> getScopes() {
if (property instanceof ProductSettingDescriptor productSettingDescriptor) {
return productSettingDescriptor.getScopes();
}
return null;
}
}
@@ -0,0 +1,34 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.app;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Abstract class that contains methods for loading configuration with gson.
*/
public abstract class BaseServerConfigurationController<T extends WebServerConfiguration> implements WebServerConfigurationController<T> {
protected Gson getGson() {
return getGsonBuilder().create();
}
protected abstract GsonBuilder getGsonBuilder();
public abstract T getServerConfiguration();
}
@@ -29,14 +29,13 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
public static final String DEFAULT_APP_ANONYMOUS_TEAM_NAME = "user";
protected final Map<String, Object> plugins;
protected String defaultUserTeam;
protected String defaultUserTeam = DEFAULT_APP_ANONYMOUS_TEAM_NAME;
protected boolean resourceManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
public BaseWebAppConfiguration() {
this.plugins = new LinkedHashMap<>();
this.defaultUserTeam = DEFAULT_APP_ANONYMOUS_TEAM_NAME;
this.resourceManagerEnabled = true;
this.enabledFeatures = null;
this.showReadOnlyConnectionInfo = false;
@@ -16,8 +16,6 @@
*/
package io.cloudbeaver.model.app;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebSessionProjectImpl;
@@ -102,8 +100,7 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return true;
}
@Nullable
protected Path loadServerConfiguration() throws DBException {
protected boolean loadServerConfiguration() throws DBException {
Path configFilePath = getMainConfigurationFilePath().toAbsolutePath();
Path configFolder = configFilePath.getParent();
@@ -120,13 +117,13 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
// Load config file
log.debug("Loading configuration from " + configFilePath);
try {
loadConfiguration(configFilePath);
getServerConfigurationController().loadServerConfiguration(configFilePath);
} catch (Exception e) {
log.error("Error parsing configuration", e);
return null;
return false;
}
return configFilePath;
return true;
}
@Nullable
@@ -153,7 +150,7 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return getLogbackConfigPath(configFolder);
}
private Path getMainConfigurationFilePath() {
protected Path getMainConfigurationFilePath() {
String configPath = DEFAULT_CONFIG_FILE_PATH;
String[] args = Platform.getCommandLineArgs();
@@ -179,8 +176,6 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return Files.exists(customConfigPath) ? customConfigPath : configPath.resolve(fileName);
}
protected abstract void loadConfiguration(Path configPath) throws DBException;
@Override
public WebProjectImpl createProjectImpl(
@NotNull WebSession webSession,
@@ -207,7 +202,7 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return VoidSecretController.INSTANCE;
}
protected static Map<String, Object> getServerConfigProps(Map<String, Object> configProps) {
public static Map<String, Object> getServerConfigProps(Map<String, Object> configProps) {
return JSONUtils.getObject(configProps, "server");
}
@@ -270,11 +265,7 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return null;
}
protected Gson getGson() {
return getGsonBuilder().create();
}
protected abstract GsonBuilder getGsonBuilder();
public abstract WebServerConfigurationController getServerConfigurationController();
@Override
public boolean isEnvironmentVariablesAccessible() {
@@ -43,6 +43,8 @@ public interface WebApplication extends DBPApplication {
WebAppConfiguration getAppConfiguration();
WebServerConfiguration getServerConfiguration();
Path getDataDirectory(boolean create);
Path getWorkspaceDirectory();
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.app;
/**
* Web server configuration.
* Contains only server configuration properties.
*/
public interface WebServerConfiguration {
boolean isDevelMode();
default String getRootURI() {
return "";
}
}
@@ -0,0 +1,34 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.app;
import org.jkiss.dbeaver.DBException;
import java.nio.file.Path;
/**
* Server configuration controller.
* Works with app server configuration (loads, updates)
*/
public interface WebServerConfigurationController<T extends WebServerConfiguration> {
/**
* Loads server configuration.
*/
void loadServerConfiguration(Path configPath) throws DBException;
}
@@ -34,8 +34,6 @@ import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -125,13 +123,13 @@ public class WebSession extends BaseWebSession
private final Map<String, DBWSessionHandler> sessionHandlers;
public WebSession(
@NotNull HttpSession httpSession,
@NotNull HttpServletRequest request,
@NotNull WebAuthApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers
) throws DBException {
super(httpSession.getId(), application);
super(request.getSession().getId(), application);
this.lastAccessTime = this.createTime;
setLocale(CommonUtils.toString(httpSession.getAttribute(ATTR_LOCALE), this.locale));
setLocale(CommonUtils.toString(request.getSession().getAttribute(ATTR_LOCALE), this.locale));
this.sessionHandlers = sessionHandlers;
//force authorization of anonymous session to avoid access error,
//because before authorization could be called by any request,
@@ -139,6 +137,7 @@ public class WebSession extends BaseWebSession
//and the order of requests is not guaranteed.
//look at CB-4747
refreshSessionAuth();
updateSessionParameters(request);
}
@Nullable
@@ -283,7 +282,7 @@ public class WebSession extends BaseWebSession
switch (type) {
case DATASOURCE_CREATED -> {
WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds);
this.connections.put(connectionInfo.getId(), connectionInfo);
this.connections.put(getConnectionId(ds), connectionInfo);
sendDataSourceUpdatedEvent = true;
}
case DATASOURCE_UPDATED -> // if settings were changed we need to send event
@@ -293,7 +292,7 @@ public class WebSession extends BaseWebSession
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
dsrc.removeDataSourceFromList(ds);
}
this.connections.remove(ds.getId());
this.connections.remove(getConnectionId(ds));
sendDataSourceUpdatedEvent = true;
}
default -> {
@@ -303,6 +302,16 @@ public class WebSession extends BaseWebSession
return sendDataSourceUpdatedEvent;
}
@NotNull
private String getConnectionId(@NotNull DBPDataSourceContainer container) {
return getConnectionId(container.getProject().getId(), container.getId());
}
@NotNull
private String getConnectionId(@NotNull String projectId, @NotNull String dsId) {
return projectId + ":" + dsId;
}
// Note: for admin use only
public synchronized void resetUserState() throws DBException {
clearAuthTokens();
@@ -473,7 +482,7 @@ public class WebSession extends BaseWebSession
var registry = getProjectById(WebAppUtils.getGlobalProjectId()).getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
connections.put(dsId, new WebConnectionInfo(this, dataSource));
connections.put(getConnectionId(dataSource), new WebConnectionInfo(this, dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_ADD, dataSource, true));
}
@@ -484,7 +493,7 @@ public class WebSession extends BaseWebSession
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
this.accessibleConnectionIds.remove(dsId);
connections.remove(dsId);
connections.remove(getConnectionId(dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_REMOVE, dataSource));
dataSource.dispose();
@@ -525,17 +534,10 @@ public class WebSession extends BaseWebSession
}
}
public synchronized void updateInfo(
HttpServletRequest request,
HttpServletResponse response
) throws DBWebException {
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
log.debug("Update session lifetime " + getSessionId() + " for user " + getUserId());
touchSession();
HttpSession httpSession = request.getSession();
this.lastRemoteAddr = request.getRemoteAddr();
this.lastRemoteUserAgent = request.getHeader("User-Agent");
this.cacheExpired = false;
if (!httpSession.isNew()) {
if (isOldHttpSessionUsed) {
try {
// Persist session
if (!isAuthorizedInSecurityManager()) {
@@ -555,6 +557,12 @@ public class WebSession extends BaseWebSession
}
}
public synchronized void updateSessionParameters(HttpServletRequest request) {
this.lastRemoteAddr = request.getRemoteAddr();
this.lastRemoteUserAgent = request.getHeader("User-Agent");
this.cacheExpired = false;
}
@Association
public List<WebConnectionInfo> getConnections() {
synchronized (connections) {
@@ -564,9 +572,21 @@ public class WebSession extends BaseWebSession
@NotNull
public WebConnectionInfo getWebConnectionInfo(@Nullable String projectId, String connectionID) throws DBWebException {
WebConnectionInfo connectionInfo;
WebConnectionInfo connectionInfo = null;
synchronized (connections) {
connectionInfo = connections.get(connectionID);
if (projectId != null) {
connectionInfo = connections.get(getConnectionId(projectId, connectionID));
} else {
addWarningMessage("Project id is not defined in request. Try to find it from connection cache");
for (Map.Entry<String, WebConnectionInfo> entry : connections.entrySet()) {
String k = entry.getKey();
WebConnectionInfo v = entry.getValue();
if (k.contains(connectionID)) {
connectionInfo = v;
break;
}
}
}
}
if (connectionInfo == null) {
WebProjectImpl project = getProjectById(projectId);
@@ -577,7 +597,7 @@ public class WebSession extends BaseWebSession
if (dataSource != null) {
connectionInfo = new WebConnectionInfo(this, dataSource);
synchronized (connections) {
connections.put(connectionID, connectionInfo);
connections.put(getConnectionId(dataSource), connectionInfo);
}
} else {
throw new DBWebException("Connection '" + connectionID + "' not found");
@@ -587,22 +607,22 @@ public class WebSession extends BaseWebSession
}
@Nullable
public WebConnectionInfo findWebConnectionInfo(String connectionID) {
public WebConnectionInfo findWebConnectionInfo(String projectId, String connectionId) {
synchronized (connections) {
return connections.get(connectionID);
return connections.get(getConnectionId(projectId, connectionId));
}
}
public void addConnection(WebConnectionInfo connectionInfo) {
synchronized (connections) {
connections.put(connectionInfo.getId(), connectionInfo);
connections.put(getConnectionId(connectionInfo.getDataSourceContainer()), connectionInfo);
}
}
public void removeConnection(WebConnectionInfo connectionInfo) {
connectionInfo.clearCache();
synchronized (connections) {
connections.remove(connectionInfo.getId());
connections.remove(getConnectionId(connectionInfo.getDataSourceContainer()));
}
}
@@ -929,7 +949,7 @@ public class WebSession extends BaseWebSession
}
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
WebConnectionInfo webConnectionInfo = findWebConnectionInfo(dataSourceContainer.getId());
WebConnectionInfo webConnectionInfo = findWebConnectionInfo(dataSourceContainer.getProject().getId(), dataSourceContainer.getId());
if (webConnectionInfo != null) {
WebDataSourceUtils.saveCredentialsInDataSource(webConnectionInfo, dataSourceContainer, configuration);
}
@@ -176,7 +176,7 @@ public class WebAppUtils {
sessionCookie.setMaxAge((int) (maxSessionIdleTime / 1000));
}
String path = getWebApplication().getRootURI();
String path = getWebApplication().getServerConfiguration().getRootURI();
if (sameSite != null) {
if (!request.isSecure()) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 24.0.1.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 24.0.2.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>24.0.1-SNAPSHOT</version>
<version>24.0.2-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.94.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.95.qualifier
Bundle-Release-Date: 20240408
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -17,6 +17,7 @@
<resource name="drivers/sqlite/xerial"/>
<resource name="drivers/mssql/new"/>
<resource name="drivers/trino"/>
<resource name="drivers/kyuubi"/>
</extension>
<!-- Bundles -->
@@ -36,6 +37,7 @@
<bundle id="drivers.sqlite.xerial" label="SQLite drivers"/>
<bundle id="drivers.mssql.new" label="SQL Server drivers"/>
<bundle id="drivers.trino" label="Trino drivers"/>
<bundle id="drivers.kyuubi" label="Apache Kyuubi drivers"/>
</extension>
<!-- Enabled drivers -->
@@ -55,6 +57,7 @@
<driver id="sqlserver:microsoft"/>
<driver id="generic:trino_jdbc"/>
<driver id="generic:duckdb_jdbc"/>
<driver id="generic:kyuubi_hive"/>
</extension>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.94-SNAPSHOT</version>
<version>1.0.95-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 24.0.1.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 24.0.2.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>24.0.1-SNAPSHOT</version>
<version>24.0.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -38,6 +38,8 @@ type ObjectPropertyInfo {
supportedConfigurationTypes: [ String! ]
# Required parameters
required: Boolean! @since(version: "23.3.1")
# Parameter scopes
scopes:[String!]
}
enum ObjectPropertyLength {
@@ -162,6 +164,16 @@ type ServerConfig {
passwordPolicyConfiguration: PasswordPolicyConfig! @since(version: "23.3.3")
}
type ProductSettingsGroup @since(version: "24.0.1") {
id: ID!
displayName: String!
}
type ProductSettings @since(version: "24.0.1") {
groups: [ProductSettingsGroup!]!
settings: [ObjectPropertyInfo!]! # each property is associated with a group by category
}
type SessionInfo {
createTime: String!
lastAccessTime: String!
@@ -530,6 +542,8 @@ extend type Query {
# Returns server config
serverConfig: ServerConfig!
productSettings: ProductSettings! @since(version: "24.0.1")
# Returns session state ( initialize if not )
sessionState: SessionInfo!
@@ -566,9 +580,9 @@ extend type Mutation {
closeSession: Boolean
# Refreshes session on server and returns its state
touchSession: Boolean @deprecated(reason: "use updateSession instead")
touchSession: Boolean @deprecated(reason: "use events to update session")
# Refreshes session on server and returns session state
updateSession: SessionInfo! @since(version: "24.0.0")
updateSession: SessionInfo! @since(version: "24.0.0") @deprecated(reason: "use events to update session")
# Refresh session connection list
refreshSessionConnections: Boolean
@@ -37,7 +37,8 @@ enum CBServerEventId {
enum CBClientEventId {
cb_client_topic_subscribe,
cb_client_topic_unsubscribe,
cb_client_projects_active
cb_client_projects_active,
cb_client_session_ping
}
# Client subscribes on topic to receive only related events
@@ -114,8 +115,12 @@ type WSSocketConnectedEvent implements CBServerEvent {
type WSSessionStateEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
lastAccessTime: Int!
remainingTime: Int!
isValid: Boolean
isCacheExpired: Boolean
locale: String!
actionParameters: Object
}
# Session expired event
@@ -182,6 +182,7 @@ public class WebDatabaseDriverInfo {
cfg.setUrl(driver.getSampleURL());
cfg.setHostName(DBConstants.HOST_LOCALHOST);
cfg.setHostPort(driver.getDefaultPort());
cfg.setDatabaseName(driver.getDefaultDatabase());
cfg.setUrl(driver.getConnectionURL(cfg));
DBPPropertyDescriptor[] properties = driver.getDataSourceProvider().getConnectionProperties(webSession.getProgressMonitor(), driver, cfg);
if (properties == null) {
@@ -0,0 +1,58 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import java.util.List;
import java.util.stream.Collectors;
public class WebProductSettings {
@NotNull
private final WebSession webSession;
@NotNull
private final List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups;
public WebProductSettings(
@NotNull WebSession webSession,
@NotNull List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups
) {
this.webSession = webSession;
this.groups = groups;
}
@NotNull
@Property
public List<WebSettingsGroupInfo> getGroups() {
return groups.stream()
.map(WebSettingsGroupInfo::new)
.collect(Collectors.toList());
}
@NotNull
@Property
public List<WebPropertyInfo> getSettings() {
return groups.stream()
.flatMap(group -> group.getSettings().stream())
.map(setting -> new WebPropertyInfo(webSession, setting))
.collect(Collectors.toList());
}
}
@@ -46,7 +46,7 @@ public class WebServerConfig {
@Property
public String getName() {
return CommonUtils.notEmpty(application.getServerName());
return CommonUtils.notEmpty(application.getServerConfiguration().getServerName());
}
@Property
@@ -61,12 +61,12 @@ public class WebServerConfig {
@Property
public String getServerURL() {
return CommonUtils.notEmpty(application.getServerURL());
return CommonUtils.notEmpty(application.getServerConfiguration().getServerURL());
}
@Property
public String getRootURI() {
return CommonUtils.notEmpty(application.getRootURI());
return CommonUtils.notEmpty(application.getServerConfiguration().getRootURI());
}
@Deprecated
@@ -127,7 +127,7 @@ public class WebServerConfig {
@Property
public boolean isDevelopmentMode() {
return application.isDevelMode();
return application.getServerConfiguration().isDevelMode();
}
@Property
@@ -142,7 +142,7 @@ public class WebServerConfig {
@Property
public long getSessionExpireTime() {
return application.getConfiguredMaxSessionIdleTime();
return application.getServerConfiguration().getMaxSessionIdleTime();
}
@Property
@@ -0,0 +1,43 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
public class WebSettingsGroupInfo {
@NotNull
private final PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor;
public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor) {
this.groupDescriptor = groupDescriptor;
}
@NotNull
@Property
public String getId() {
return groupDescriptor.getFullId();
}
@NotNull
@Property
public String getDisplayName() {
return groupDescriptor.getDisplayName();
}
}
@@ -56,6 +56,7 @@ public class CBAppConfig extends BaseAuthWebAppConfiguration implements WebAuthC
private boolean redirectOnFederatedAuth;
private boolean anonymousAccessEnabled;
private boolean grantConnectionsAccessToAnonymousTeam;
private boolean systemVariablesResolvingEnabled;
@Deprecated
private String anonymousUserRole;
private String anonymousUserTeam;
@@ -85,6 +86,7 @@ public class CBAppConfig extends BaseAuthWebAppConfiguration implements WebAuthC
this.forwardProxy = false;
this.linkExternalCredentialsWithUser = true;
this.grantConnectionsAccessToAnonymousTeam = false;
this.systemVariablesResolvingEnabled = false;
}
public CBAppConfig(CBAppConfig src) {
@@ -106,6 +108,7 @@ public class CBAppConfig extends BaseAuthWebAppConfiguration implements WebAuthC
this.forwardProxy = src.forwardProxy;
this.linkExternalCredentialsWithUser = src.linkExternalCredentialsWithUser;
this.grantConnectionsAccessToAnonymousTeam = src.grantConnectionsAccessToAnonymousTeam;
this.systemVariablesResolvingEnabled = src.systemVariablesResolvingEnabled;
}
@Override
@@ -296,4 +299,8 @@ public class CBAppConfig extends BaseAuthWebAppConfiguration implements WebAuthC
public boolean isDriverForceEnabled(@NotNull String driverId) {
return ArrayUtils.containsIgnoreCase(getEnabledDrivers(), driverId);
}
public boolean isSystemVariablesResolvingEnabled() {
return systemVariablesResolvingEnabled;
}
}
File diff suppressed because it is too large Load Diff
@@ -36,37 +36,45 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
import java.util.List;
public class CBApplicationCE extends CBApplication {
public class CBApplicationCE extends CBApplication<CBServerConfig> {
private static final Log log = Log.getLog(CBApplicationCE.class);
private final CBServerConfigurationControllerEmbedded<CBServerConfig> serverConfigController;
public CBApplicationCE() {
serverConfigController = new CBServerConfigurationControllerEmbedded<>(new CBServerConfig());
}
@Override
public SMController createSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException {
return new EmbeddedSecurityControllerFactory().createSecurityService(
this,
databaseConfiguration,
getServerConfiguration().getDatabaseConfiguration(),
credentialsProvider,
securityManagerConfiguration
getServerConfigurationController().getSecurityManagerConfiguration()
);
}
@Override
public SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException {
return new EmbeddedSecurityControllerFactory().createSecurityService(
this,
databaseConfiguration,
getServerConfiguration().getDatabaseConfiguration(),
credentialsProvider,
securityManagerConfiguration
getServerConfigurationController().getSecurityManagerConfiguration()
);
}
protected SMAdminController createGlobalSecurityController() throws DBException {
return new EmbeddedSecurityControllerFactory().createSecurityService(
this,
databaseConfiguration,
getServerConfiguration().getDatabaseConfiguration(),
new NoAuthCredentialsProvider(),
securityManagerConfiguration
getServerConfigurationController().getSecurityManagerConfiguration()
);
}
@Override
public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace) throws DBException {
@@ -79,6 +87,11 @@ public class CBApplicationCE extends CBApplication {
return new LocalFileController(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath().resolve(DBFileController.DATA_FOLDER));
}
@Override
public CBServerConfigurationControllerEmbedded<CBServerConfig> getServerConfigurationController() {
return serverConfigController;
}
protected void shutdown() {
try {
if (securityController instanceof CBEmbeddedSecurityController) {
@@ -99,4 +112,5 @@ public class CBApplicationCE extends CBApplication {
((CBEmbeddedSecurityController) securityController).finishConfiguration(adminName, adminPassword, authInfoList);
}
}
}
@@ -25,6 +25,7 @@ public class CBConstants {
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 PARAM_SERVER_CONFIGURATION = "server";
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_HOST = "serverHost";
public static final String PARAM_SERVER_NAME = "serverName";
@@ -35,7 +36,10 @@ public class CBConstants {
public static final String PARAM_SERVICES_URI = "serviceURI";
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
public static final String PARAM_WORKSPACE_LOCATION = "workspaceLocation";
//legacy path to a product.conf file
@Deprecated
public static final String PARAM_PRODUCT_CONFIGURATION = "productConfiguration";
public static final String PARAM_PRODUCT_SETTINGS = "productSettings";
public static final String PARAM_EXTERNAL_PROPERTIES = "externalProperties";
public static final String PARAM_STATIC_CONTENT = "staticContent";
public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas";
@@ -68,12 +68,13 @@ public class CBPlatform extends BasePlatformImpl {
public static final String PLUGIN_ID = "io.cloudbeaver.server"; //$NON-NLS-1$
private static final Log log = Log.getLog(CBPlatform.class);
private static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
public static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
public static final String TEMP_FILE_IMPORT_FOLDER = "temp-import-files";
public static final String WORK_DATA_FOLDER_NAME = ".work-data";
@Nullable
private static CBApplication application = null;
private static CBApplication<?> application = null;
private Path tempFolder;
@@ -133,6 +134,7 @@ public class CBPlatform extends BasePlatformImpl {
protected IStatus run(DBRProgressMonitor monitor) {
try {
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -189,7 +191,7 @@ public class CBPlatform extends BasePlatformImpl {
@NotNull
@Override
public CBApplication getApplication() {
public CBApplication<?> getApplication() {
return application;
}
@@ -0,0 +1,194 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import com.google.gson.annotations.SerializedName;
import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.model.app.WebServerConfiguration;
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
public class CBServerConfig implements WebServerConfiguration {
private static final Log log = Log.getLog(CBServerConfig.class);
protected String serverURL;
protected int serverPort = CBConstants.DEFAULT_SERVER_PORT;
private String serverHost = null;
private String serverName = null;
private String sslConfigurationPath = null;
private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT;
private String rootURI = CBConstants.DEFAULT_ROOT_URI;
private String serviceURI = CBConstants.DEFAULT_SERVICES_URI;
private String workspaceLocation = CBConstants.DEFAULT_WORKSPACE_LOCATION;
private String driversLocation = CBConstants.DEFAULT_DRIVERS_LOCATION;
@SerializedName("expireSessionAfterPeriod")
private long maxSessionIdleTime = CBAuthConstants.MAX_SESSION_IDLE_TIME;
private boolean develMode = false;
private boolean enableSecurityManager = false;
private final Map<String, Object> productSettings = new HashMap<>();
@SerializedName("database")
private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig();
private String staticContent = "";
public String getServerURL() {
if (serverURL == null) {
String hostName = serverHost;
if (CommonUtils.isEmpty(hostName)) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = CBApplication.HOST_LOCALHOST;
}
}
serverURL = "http://" + hostName + ":" + serverPort;
}
return serverURL;
}
public int getServerPort() {
return serverPort;
}
public String getServerHost() {
return serverHost;
}
public String getServerName() {
return serverName;
}
public String getSslConfigurationPath() {
return sslConfigurationPath;
}
public String getContentRoot() {
return contentRoot;
}
public String getRootURI() {
return rootURI;
}
public String getServicesURI() {
return serviceURI;
}
public String getWorkspaceLocation() {
return workspaceLocation;
}
public String getDriversLocation() {
return driversLocation;
}
public WebDatabaseConfig getDatabaseConfiguration() {
return databaseConfiguration;
}
public String getStaticContent() {
return staticContent;
}
public void setServerURL(String serverURL) {
this.serverURL = serverURL;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public void setServerHost(String serverHost) {
this.serverHost = serverHost;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public void setSslConfigurationPath(String sslConfigurationPath) {
this.sslConfigurationPath = sslConfigurationPath;
}
public void setContentRoot(String contentRoot) {
this.contentRoot = contentRoot;
}
public void setRootURI(String rootURI) {
this.rootURI = rootURI;
}
public void setServicesURI(String servicesURI) {
this.serviceURI = servicesURI;
}
public void setWorkspaceLocation(String workspaceLocation) {
this.workspaceLocation = workspaceLocation;
}
public void setDriversLocation(String driversLocation) {
this.driversLocation = driversLocation;
}
public void setMaxSessionIdleTime(long maxSessionIdleTime) {
this.maxSessionIdleTime = maxSessionIdleTime;
}
public void setDevelMode(boolean develMode) {
this.develMode = develMode;
}
public void setEnableSecurityManager(boolean enableSecurityManager) {
this.enableSecurityManager = enableSecurityManager;
}
public void setDatabaseConfiguration(WebDatabaseConfig databaseConfiguration) {
this.databaseConfiguration = databaseConfiguration;
}
public void setStaticContent(String staticContent) {
this.staticContent = staticContent;
}
@Override
public boolean isDevelMode() {
return develMode;
}
public long getMaxSessionIdleTime() {
return maxSessionIdleTime;
}
public boolean isEnableSecurityManager() {
return enableSecurityManager;
}
@NotNull
public Map<String, Object> getProductSettings() {
return productSettings;
}
}
@@ -0,0 +1,619 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import io.cloudbeaver.model.app.BaseServerConfigurationController;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.service.security.PasswordPolicyConfiguration;
import io.cloudbeaver.service.security.SMControllerConfiguration;
import io.cloudbeaver.utils.WebAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ModelPreferences;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.IVariableResolver;
import org.jkiss.dbeaver.utils.ContentUtils;
import org.jkiss.dbeaver.utils.PrefUtils;
import org.jkiss.dbeaver.utils.SystemVariablesResolver;
import org.jkiss.utils.CommonUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class CBServerConfigurationController<T extends CBServerConfig>
extends BaseServerConfigurationController<T> {
private static final Log log = Log.getLog(CBServerConfigurationController.class);
// Configurations
protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration();
private final T serverConfiguration;
private final CBAppConfig appConfiguration = new CBAppConfig();
private Map<String, String> externalProperties = new LinkedHashMap<>();
private Map<String, Object> originalConfigurationProperties = new LinkedHashMap<>();
protected CBServerConfigurationController(T serverConfiguration) {
this.serverConfiguration = serverConfiguration;
}
public String getAuthServiceURL() {
return Stream.of(serverConfiguration.getServerURL(),
serverConfiguration.getRootURI(),
serverConfiguration.getServicesURI())
.map(WebAppUtils::removeSideSlashes)
.filter(CommonUtils::isNotEmpty)
.collect(Collectors.joining("/"));
}
@Override
public void loadServerConfiguration(Path configPath) throws DBException {
log.debug("Using configuration [" + configPath + "]");
if (!Files.exists(configPath)) {
log.error("Configuration file " + configPath + " doesn't exist. Use defaults.");
} else {
loadConfiguration(configPath);
}
// Try to load configuration from runtime app config file
Path runtimeConfigPath = getRuntimeAppConfigPath();
if (Files.exists(runtimeConfigPath)) {
log.debug("Runtime configuration [" + runtimeConfigPath.toAbsolutePath() + "]");
loadConfiguration(runtimeConfigPath);
}
// Set default preferences
PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(),
ModelPreferences.UI_DRIVERS_HOME,
getServerConfiguration().getDriversLocation());
}
public void loadConfiguration(Path configPath) throws DBException {
CBAppConfig prevConfig = new CBAppConfig(appConfiguration);
Map<String, Object> configProps = readConfiguration(configPath);
try {
parseConfiguration(configProps);
} catch (Exception e) {
throw new DBException("Error parsing server configuration", e);
}
// Backward compatibility: load configs map
appConfiguration.loadLegacyCustomConfigs();
// Merge new config with old one
mergeOldConfiguration(prevConfig);
patchConfigurationWithProperties(getServerConfiguration().getProductSettings());
}
protected void parseConfiguration(Map<String, Object> configProps) throws DBException {
Map<String, Object> serverConfig = JSONUtils.getObject(configProps, "server");
readExternalProperties(serverConfig);
patchConfigurationWithProperties(configProps); // patch again because properties can be changed
Gson gson = getGson();
Map<String, Object> currentConfigurationAsMap = gson.fromJson(gson.toJson(getServerConfiguration()),
JSONUtils.MAP_TYPE_TOKEN);
serverConfig = WebAppUtils.mergeConfigurations(currentConfigurationAsMap, serverConfig);
gson.fromJson(
gson.toJson(serverConfig),
getServerConfiguration().getClass()
);
parseServerConfiguration();
//SM config
gson.fromJson(
gson.toJson(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)),
SMControllerConfiguration.class
);
// App config
Map<String, Object> appConfig = JSONUtils.getObject(configProps, "app");
validateConfiguration(appConfig);
gson.fromJson(gson.toJson(appConfig), CBAppConfig.class);
readProductConfiguration(serverConfig, gson);
}
public T parseServerConfiguration() {
var config = getServerConfiguration();
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = CBApplication.HOST_LOCALHOST;
}
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
}
var homeDirectory = CBApplication.getInstance().getHomeDirectory().toString();
config.setContentRoot(WebAppUtils.getRelativePath(config.getContentRoot(), homeDirectory));
config.setRootURI(readRootUri(config.getRootURI()));
config.setDriversLocation(WebAppUtils.getRelativePath(config.getDriversLocation(), homeDirectory));
config.setWorkspaceLocation(WebAppUtils.getRelativePath(config.getWorkspaceLocation(), homeDirectory));
String staticContentsFile = config.getStaticContent();
if (!CommonUtils.isEmpty(staticContentsFile)) {
try {
config.setStaticContent(Files.readString(Path.of(staticContentsFile)));
} catch (IOException e) {
log.error("Error reading static contents from " + staticContentsFile, e);
}
}
return config;
}
protected void validateConfiguration(Map<String, Object> appConfig) throws DBException {
}
private void readExternalProperties(Map<String, Object> serverConfig) {
String externalPropertiesFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_EXTERNAL_PROPERTIES);
if (!CommonUtils.isEmpty(externalPropertiesFile)) {
Properties props = new Properties();
try (InputStream is = Files.newInputStream(Path.of(externalPropertiesFile))) {
props.load(is);
} catch (IOException e) {
log.error("Error loading external properties from " + externalPropertiesFile, e);
}
for (String propName : props.stringPropertyNames()) {
this.externalProperties.put(propName, props.getProperty(propName));
}
}
}
protected void mergeOldConfiguration(CBAppConfig prevConfig) {
Map<String, Object> mergedPlugins = Stream.concat(
prevConfig.getPlugins().entrySet().stream(),
appConfiguration.getPlugins().entrySet().stream()
)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (o, o2) -> o2));
appConfiguration.setPlugins(mergedPlugins);
Set<SMAuthProviderCustomConfiguration> mergedAuthProviders = Stream.concat(
prevConfig.getAuthCustomConfigurations().stream(),
appConfiguration.getAuthCustomConfigurations().stream()
)
.collect(Collectors.toCollection(LinkedHashSet::new));
appConfiguration.setAuthProvidersConfigurations(mergedAuthProviders);
}
protected void readProductConfiguration(Map<String, Object> serverConfig, Gson gson)
throws DBException {
// legacy configuration with path to product.conf file
if (!serverConfig.containsKey(CBConstants.PARAM_PRODUCT_SETTINGS)
&& serverConfig.get(CBConstants.PARAM_PRODUCT_CONFIGURATION) instanceof String
) {
String productConfigPath = WebAppUtils.getRelativePath(
JSONUtils.getString(
serverConfig,
CBConstants.PARAM_PRODUCT_CONFIGURATION,
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
),
CBApplication.getInstance().getHomeDirectory().toString()
);
if (!CommonUtils.isEmpty(productConfigPath)) {
File productConfigFile = new File(productConfigPath);
if (!productConfigFile.exists()) {
log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'");
} else {
log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile),
StandardCharsets.UTF_8)) {
serverConfiguration.getProductSettings()
.putAll(WebAppUtils.flattenMap(JSONUtils.parseMap(gson, reader)));
} catch (Exception e) {
throw new DBException("Error reading product configuration", e);
}
}
}
}
// Add product config from runtime
File rtConfig = getRuntimeProductConfigFilePath().toFile();
if (rtConfig.exists()) {
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
var runtimeProductSettings = JSONUtils.parseMap(gson, reader);
var productSettings = serverConfiguration.getProductSettings();
productSettings.putAll(runtimeProductSettings);
Map<String, Object> flattenConfig = WebAppUtils.flattenMap(productSettings);
productSettings.clear();
productSettings.putAll(flattenConfig);
} catch (Exception e) {
throw new DBException("Error reading product runtime configuration", e);
}
}
}
protected Map<String, Object> readConnectionsPermissionsConfiguration(Path parentPath) {
String permissionsConfigPath = WebAppUtils.getRelativePath(CBConstants.DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION,
parentPath);
File permissionsConfigFile = new File(permissionsConfigPath);
if (permissionsConfigFile.exists()) {
log.debug("Load permissions configuration from '" + permissionsConfigFile.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(permissionsConfigFile),
StandardCharsets.UTF_8)) {
return JSONUtils.parseMap(getGson(), reader);
} catch (Exception e) {
log.error("Error reading permissions configuration", e);
}
}
return null;
}
protected Map<String, Object> readConfiguration(Path configPath) throws DBException {
Map<String, Object> configProps = new LinkedHashMap<>();
if (Files.exists(configPath)) {
log.debug("Read configuration [" + configPath.toAbsolutePath() + "]");
// saves original configuration file
this.originalConfigurationProperties.putAll(readConfigurationFile(configPath));
configProps.putAll(readConfigurationFile(configPath));
patchConfigurationWithProperties(configProps); // patch original properties
}
return configProps;
}
public Map<String, Object> readConfigurationFile(Path path) throws DBException {
try (Reader reader = new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8)) {
return JSONUtils.parseMap(getGson(), reader);
} catch (Exception e) {
throw new DBException("Error parsing server configuration", e);
}
}
protected GsonBuilder getGsonBuilder() {
// Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431)
InstanceCreator<CBAppConfig> appConfigCreator = type -> appConfiguration;
InstanceCreator<DataSourceNavigatorSettings> navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings();
InstanceCreator<SMControllerConfiguration> smConfigCreator = type -> securityManagerConfiguration;
InstanceCreator<T> serverConfigCreator = type -> serverConfiguration;
InstanceCreator<PasswordPolicyConfiguration> smPasswordPoliceConfigCreator =
type -> securityManagerConfiguration.getPasswordPolicyConfiguration();
return new GsonBuilder()
.setLenient()
.registerTypeAdapter(getServerConfiguration().getClass(), serverConfigCreator)
.registerTypeAdapter(CBAppConfig.class, appConfigCreator)
.registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator)
.registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator)
.registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator);
}
protected void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException {
saveRuntimeConfig(
serverConfiguration.getServerName(),
serverConfiguration.getServerURL(),
serverConfiguration.getMaxSessionIdleTime(),
appConfiguration,
credentialsProvider
);
}
protected void saveRuntimeConfig(
String newServerName,
String newServerURL,
long sessionExpireTime,
CBAppConfig appConfig,
SMCredentialsProvider credentialsProvider
) throws DBException {
if (newServerName == null) {
throw new DBException("Invalid server configuration, server name cannot be empty");
}
Map<String, Object> configurationProperties = collectConfigurationProperties(newServerName,
newServerURL,
sessionExpireTime,
appConfig);
writeRuntimeConfig(getRuntimeAppConfigPath(), configurationProperties);
}
private void writeRuntimeConfig(Path runtimeConfigPath, Map<String, Object> configurationProperties)
throws DBException {
if (Files.exists(runtimeConfigPath)) {
ContentUtils.makeFileBackup(runtimeConfigPath);
}
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigPath.toFile()),
StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder()
.setLenient()
.setPrettyPrinting()
.create();
gson.toJson(configurationProperties, out);
} catch (IOException e) {
throw new DBException("Error writing runtime configuration", e);
}
}
protected Map<String, Object> collectConfigurationProperties(
String newServerName,
String newServerURL,
long sessionExpireTime,
CBAppConfig appConfig
) {
Map<String, Object> rootConfig = new LinkedHashMap<>();
{
var originServerConfig = BaseWebApplication.getServerConfigProps(this.originalConfigurationProperties); // get server properties from original configuration file
var serverConfigProperties = collectServerConfigProperties(newServerName,
newServerURL,
sessionExpireTime,
originServerConfig);
rootConfig.put("server", serverConfigProperties);
}
{
var appConfigProperties = new LinkedHashMap<String, Object>();
Map<String, Object> oldAppConfig = JSONUtils.getObject(this.originalConfigurationProperties, "app");
rootConfig.put("app", appConfigProperties);
copyConfigValue(
oldAppConfig, appConfigProperties, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"supportsCustomConnections",
appConfig.isSupportsCustomConnections());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"publicCredentialsSaveEnabled",
appConfig.isPublicCredentialsSaveEnabled());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"adminCredentialsSaveEnabled",
appConfig.isAdminCredentialsSaveEnabled());
copyConfigValue(
oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
copyConfigValue(
oldAppConfig, appConfigProperties, "forwardProxy", appConfig.isEnabledForwardProxy());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"linkExternalCredentialsWithUser",
appConfig.isLinkExternalCredentialsWithUser());
copyConfigValue(
oldAppConfig, appConfigProperties, "redirectOnFederatedAuth", appConfig.isRedirectOnFederatedAuth());
copyConfigValue(
oldAppConfig,
appConfigProperties,
CBConstants.PARAM_RESOURCE_MANAGER_ENABLED,
appConfig.isResourceManagerEnabled());
copyConfigValue(
oldAppConfig,
appConfigProperties,
CBConstants.PARAM_SHOW_READ_ONLY_CONN_INFO,
appConfig.isShowReadOnlyConnectionInfo());
copyConfigValue(
oldAppConfig,
appConfigProperties,
CBConstants.PARAM_CONN_GRANT_ANON_ACCESS,
appConfig.isGrantConnectionsAccessToAnonymousTeam());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"systemVariablesResolvingEnabled",
appConfig.isSystemVariablesResolvingEnabled()
);
Map<String, Object> resourceQuotas = new LinkedHashMap<>();
Map<String, Object> originResourceQuotas = JSONUtils.getObject(oldAppConfig,
CBConstants.PARAM_RESOURCE_QUOTAS);
for (Map.Entry<String, Object> mp : appConfig.getResourceQuotas().entrySet()) {
copyConfigValue(originResourceQuotas, resourceQuotas, mp.getKey(), mp.getValue());
}
appConfigProperties.put(CBConstants.PARAM_RESOURCE_QUOTAS, resourceQuotas);
{
// Save only differences in def navigator settings
DBNBrowseSettings navSettings = appConfig.getDefaultNavigatorSettings();
var navigatorProperties = new LinkedHashMap<String, Object>();
appConfigProperties.put("defaultNavigatorSettings", navigatorProperties);
if (navSettings.isShowSystemObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowSystemObjects()) {
navigatorProperties.put("showSystemObjects", navSettings.isShowSystemObjects());
}
if (navSettings.isShowUtilityObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowUtilityObjects()) {
navigatorProperties.put("showUtilityObjects", navSettings.isShowUtilityObjects());
}
if (navSettings.isShowOnlyEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowOnlyEntities()) {
navigatorProperties.put("showOnlyEntities", navSettings.isShowOnlyEntities());
}
if (navSettings.isMergeEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isMergeEntities()) {
navigatorProperties.put("mergeEntities", navSettings.isMergeEntities());
}
if (navSettings.isHideFolders() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideFolders()) {
navigatorProperties.put("hideFolders", navSettings.isHideFolders());
}
if (navSettings.isHideSchemas() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideSchemas()) {
navigatorProperties.put("hideSchemas", navSettings.isHideSchemas());
}
if (navSettings.isHideVirtualModel() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideVirtualModel()) {
navigatorProperties.put("hideVirtualModel", navSettings.isHideVirtualModel());
}
}
if (appConfig.getEnabledFeatures() != null) {
appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures()));
}
if (appConfig.getEnabledAuthProviders() != null) {
appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
}
if (appConfig.getEnabledDrivers() != null) {
appConfigProperties.put("enabledDrivers", Arrays.asList(appConfig.getEnabledDrivers()));
}
if (appConfig.getDisabledDrivers() != null) {
appConfigProperties.put("disabledDrivers", Arrays.asList(appConfig.getDisabledDrivers()));
}
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
appConfigProperties.put("plugins", appConfig.getPlugins());
}
if (!CommonUtils.isEmpty(appConfig.getAuthCustomConfigurations())) {
appConfigProperties.put("authConfigurations", appConfig.getAuthCustomConfigurations());
}
}
return rootConfig;
}
@NotNull
protected Map<String, Object> collectServerConfigProperties(
String newServerName,
String newServerURL,
long sessionExpireTime,
Map<String, Object> originServerConfig
) {
var serverConfigProperties = new LinkedHashMap<String, Object>();
if (!CommonUtils.isEmpty(newServerName)) {
copyConfigValue(originServerConfig,
serverConfigProperties,
CBConstants.PARAM_SERVER_NAME,
newServerName);
}
if (!CommonUtils.isEmpty(newServerURL)) {
copyConfigValue(
originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, newServerURL);
}
if (sessionExpireTime > 0) {
copyConfigValue(
originServerConfig,
serverConfigProperties,
CBConstants.PARAM_SESSION_EXPIRE_PERIOD,
sessionExpireTime);
}
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
CBConstants.PARAM_PRODUCT_SETTINGS);
if (!CommonUtils.isEmpty(getServerConfiguration().getProductSettings())) {
for (Map.Entry<String, Object> mp : getServerConfiguration().getProductSettings().entrySet()) {
copyConfigValue(oldProductRuntimeConfig, productConfigProperties, mp.getKey(), mp.getValue());
}
serverConfigProperties.put(CBConstants.PARAM_PRODUCT_SETTINGS, productConfigProperties);
}
return serverConfigProperties;
}
////////////////////////////////////////////////////////////////////////
// Configuration utils
private void patchConfigurationWithProperties(Map<String, Object> configProps) {
IVariableResolver varResolver = new SystemVariablesResolver() {
@Override
public String get(String name) {
String propValue = externalProperties.get(name);
if (propValue != null) {
return propValue;
}
return super.get(name);
}
};
BaseWebApplication.patchConfigurationWithProperties(configProps, varResolver);
}
// gets info about patterns from original configuration file and saves it to runtime config
protected void copyConfigValue(
Map<String, Object> oldConfig,
Map<String, Object> newConfig,
String key,
Object defaultValue
) {
Object value = oldConfig.get(key);
if (value instanceof Map && defaultValue instanceof Map) {
Map<String, Object> subValue = new LinkedHashMap<>();
Map<String, Object> oldConfigValue = JSONUtils.getObject(oldConfig, key);
for (Map.Entry<String, Object> entry : oldConfigValue.entrySet()) {
copyConfigValue(oldConfigValue, subValue, entry.getKey(), ((Map) defaultValue).get(entry.getKey()));
}
newConfig.put(key, subValue);
} else {
Object newConfigValue = WebAppUtils.getExtractedValue(oldConfig.get(key), defaultValue);
newConfig.put(key, newConfigValue);
}
}
@NotNull
protected Path getRuntimeAppConfigPath() {
return getDataDirectory(true).resolve(CBConstants.RUNTIME_APP_CONFIG_FILE_NAME);
}
@NotNull
protected Path getRuntimeProductConfigFilePath() {
return getDataDirectory(false).resolve(CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME);
}
@NotNull
public Path getDataDirectory(boolean create) {
File dataDir = new File(serverConfiguration.getWorkspaceLocation(), CBConstants.RUNTIME_DATA_DIR_NAME);
if (create && !dataDir.exists()) {
if (!dataDir.mkdirs()) {
log.error("Can't create data directory '" + dataDir.getAbsolutePath() + "'");
}
}
return dataDir.toPath();
}
public void saveProductConfiguration(Map<String, Object> productConfiguration) throws DBException {
Map<String, Object> productSettings = getServerConfiguration().getProductSettings();
Map<String, Object> mergedConfig = WebAppUtils.mergeConfigurations(productSettings, productConfiguration);
productSettings.clear();
productSettings.putAll(WebAppUtils.flattenMap(mergedConfig));
}
public T getServerConfiguration() {
return serverConfiguration;
}
public CBAppConfig getAppConfiguration() {
return appConfiguration;
}
public Map<String, Object> getProductConfiguration() {
return getServerConfiguration().getProductSettings();
}
public SMControllerConfiguration getSecurityManagerConfiguration() {
return securityManagerConfiguration;
}
private String readRootUri(String uri) {
//slashes are needed to correctly display static resources on ui
if (!uri.endsWith("/")) {
uri = uri + '/';
}
if (!uri.startsWith("/")) {
uri = '/' + uri;
}
return uri;
}
}
@@ -0,0 +1,108 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.utils.CommonUtils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Server configuration controller for embedded products.
*/
public class CBServerConfigurationControllerEmbedded<T extends CBServerConfig> extends CBServerConfigurationController<T> {
private static final Log log = Log.getLog(CBServerConfigurationControllerEmbedded.class);
public CBServerConfigurationControllerEmbedded(T serverConfig) {
super(serverConfig);
}
@NotNull
@Override
protected Map<String, Object> collectServerConfigProperties(
String newServerName,
String newServerURL,
long sessionExpireTime,
Map<String, Object> originServerConfig
) {
Map<String, Object> serverConfigProperties = super.collectServerConfigProperties(
newServerName, newServerURL, sessionExpireTime, originServerConfig);
var databaseConfigProperties = new LinkedHashMap<String, Object>();
Map<String, Object> oldRuntimeDBConfig = JSONUtils.getObject(originServerConfig,
CBConstants.PARAM_DB_CONFIGURATION);
Gson gson = getGson();
Map<String, Object> dbConfigMap = gson.fromJson(
gson.toJsonTree(getServerConfiguration().getDatabaseConfiguration()),
JSONUtils.MAP_TYPE_TOKEN
);
if (!CommonUtils.isEmpty(dbConfigMap)) {
for (Map.Entry<String, Object> mp : dbConfigMap.entrySet()) {
copyConfigValue(oldRuntimeDBConfig, databaseConfigProperties, mp.getKey(), mp.getValue());
}
serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties);
}
savePasswordPolicyConfig(originServerConfig, serverConfigProperties);
return serverConfigProperties;
}
private void savePasswordPolicyConfig(Map<String, Object> originServerConfig, Map<String, Object> serverConfigProperties) {
// save password policy configuration
var passwordPolicyProperties = new LinkedHashMap<String, Object>();
var oldRuntimePasswordPolicyConfig = JSONUtils.getObject(
JSONUtils.getObject(originServerConfig, CBConstants.PARAM_SM_CONFIGURATION),
CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION
);
Gson gson = getGson();
Map<String, Object> passwordPolicyConfig = gson.fromJson(
gson.toJsonTree(securityManagerConfiguration.getPasswordPolicyConfiguration()),
JSONUtils.MAP_TYPE_TOKEN
);
if (!CommonUtils.isEmpty(passwordPolicyConfig)) {
for (Map.Entry<String, Object> mp : passwordPolicyConfig.entrySet()) {
copyConfigValue(oldRuntimePasswordPolicyConfig, passwordPolicyProperties, mp.getKey(), mp.getValue());
}
serverConfigProperties.put(
CBConstants.PARAM_SM_CONFIGURATION,
Map.of(CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION, passwordPolicyProperties)
);
}
}
@Override
protected GsonBuilder getGsonBuilder() {
GsonBuilder gsonBuilder = super.getGsonBuilder();
var databaseConfiguration = getServerConfiguration().getDatabaseConfiguration();
InstanceCreator<WebDatabaseConfig> dbConfigCreator = type -> databaseConfiguration;
InstanceCreator<WebDatabaseConfig.Pool> dbPoolConfigCreator = type -> databaseConfiguration.getPool();
return gsonBuilder
.registerTypeAdapter(WebDatabaseConfig.class, dbConfigCreator)
.registerTypeAdapter(WebDatabaseConfig.Pool.class, dbPoolConfigCreator);
}
}
@@ -31,11 +31,20 @@ import java.nio.file.Path;
public class WSDeleteTempFileHandler implements WSEventHandler<WSEventDeleteTempFile> {
private static final Log log = Log.getLog(WSDeleteTempFileHandler.class);
private static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
public void resetTempFolder(String sessionId) {
Path path = CBPlatform.getInstance()
.getTempFolder(new VoidProgressMonitor(), TEMP_FILE_FOLDER)
.getTempFolder(new VoidProgressMonitor(), CBPlatform.TEMP_FILE_FOLDER)
.resolve(sessionId);
if (Files.exists(path)) {
try {
IOUtils.deleteDirectory(path);
} catch (IOException e) {
log.error("Error deleting temp path", e);
}
}
path = CBPlatform.getInstance()
.getTempFolder(new VoidProgressMonitor(), CBPlatform.TEMP_FILE_IMPORT_FOLDER)
.resolve(sessionId);
if (Files.exists(path)) {
try {
@@ -81,7 +81,7 @@ public class WSObjectPermissionUpdatedEventHandler extends WSDefaultEventHandler
return;
}
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
isAccessibleNow = webSession.findWebConnectionInfo(objectId) != null;
isAccessibleNow = webSession.findWebConnectionInfo(project.getId(), objectId) != null;
if (isAccessibleNow) {
return;
}
@@ -40,7 +40,7 @@ public class WSUserSecretEventHandlerImpl extends WSDefaultEventHandler<WSUserSe
activeUserSession.addSessionEvent(event);
return;
}
var connectionInfo = ((WebSession) activeUserSession).findWebConnectionInfo(event.getDataSourceId());
var connectionInfo = ((WebSession) activeUserSession).findWebConnectionInfo(event.getProjectId(), event.getDataSourceId());
if (connectionInfo == null) {
log.debug("Connection " + event.getDataSourceId() + " is not found in session " + activeUserSession.getSessionId());
return;
@@ -18,6 +18,7 @@ package io.cloudbeaver.server.jetty;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBServerConfig;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
@@ -37,6 +38,7 @@ import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -68,12 +70,12 @@ public class CBJettyServer {
}
public void runServer() {
CBApplication application = CBApplication.getInstance();
try {
CBServerConfig serverConfiguration = application.getServerConfiguration();
JettyServer server;
int serverPort = application.getServerPort();
String serverHost = application.getServerHost();
Path sslPath = application.getSslConfigurationPath();
int serverPort = serverConfiguration.getServerPort();
String serverHost = serverConfiguration.getServerHost();
Path sslPath = getSslConfigurationPath();
boolean sslConfigurationExists = sslPath != null && Files.exists(sslPath);
if (sslConfigurationExists) {
@@ -97,8 +99,8 @@ public class CBJettyServer {
{
// Handler configuration
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setResourceBase(application.getContentRoot());
String rootURI = application.getRootURI();
servletContextHandler.setResourceBase(serverConfiguration.getContentRoot());
String rootURI = serverConfiguration.getRootURI();
servletContextHandler.setContextPath(rootURI);
ServletHolder staticServletHolder = new ServletHolder("static", new CBStaticServlet());
@@ -106,11 +108,11 @@ public class CBJettyServer {
servletContextHandler.addServlet(staticServletHolder, "/*");
ServletHolder imagesServletHolder = new ServletHolder("images", new CBImageServlet());
servletContextHandler.addServlet(imagesServletHolder, application.getServicesURI() + "images/*");
servletContextHandler.addServlet(imagesServletHolder, serverConfiguration.getServicesURI() + "images/*");
servletContextHandler.addServlet(new ServletHolder("status", new CBStatusServlet()), "/status");
servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), application.getServicesURI() + "gql/*");
servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), serverConfiguration.getServicesURI() + "gql/*");
servletContextHandler.addEventListener(new CBServerContextListener());
// Add extensions from services
@@ -118,7 +120,7 @@ public class CBJettyServer {
CBJettyServletContext servletContext = new CBJettyServletContext(servletContextHandler);
for (DBWServiceBindingServlet wsd : WebServiceRegistry.getInstance().getWebServices(DBWServiceBindingServlet.class)) {
try {
wsd.addServlets(application, servletContext);
wsd.addServlets(this.application, servletContext);
} catch (DBException e) {
log.error(e.getMessage(), e);
}
@@ -133,8 +135,8 @@ public class CBJettyServer {
wsContainer.setIdleTimeout(Duration.ofMinutes(5));
// Add websockets
wsContainer.addMapping(
application.getServicesURI() + "ws/*",
new CBJettyWebSocketManager(application.getSessionManager())
serverConfiguration.getServicesURI() + "ws/*",
new CBJettyWebSocketManager(this.application.getSessionManager())
);
}
);
@@ -172,6 +174,16 @@ public class CBJettyServer {
}
}
@Nullable
private Path getSslConfigurationPath() {
var sslConfigurationPath = application.getServerConfiguration().getSslConfigurationPath();
if (sslConfigurationPath == null) {
return null;
}
var sslConfiguration = Path.of(sslConfigurationPath);
return sslConfiguration.isAbsolute() ? sslConfiguration : application.getHomeDirectory().resolve(sslConfiguration);
}
private void initSessionManager(
@NotNull CBApplication application,
@NotNull ServletContextHandler servletContextHandler
@@ -188,31 +200,19 @@ public class CBJettyServer {
}
}
SessionHandler sessionHandler = new SessionHandler()/* {
public HttpCookie access(HttpSession session, boolean secure) {
HttpCookie cookie = getSessionCookie(session, _context == null ? "/" : (_context.getContextPath()), secure);
return cookie;
}
@Override
public int getRefreshCookieAge() {
// Refresh cookie always (we need it for FA requests)
return 1;
}
}*/;
var maxIdleSeconds = application.getMaxSessionIdleTime();
SessionHandler sessionHandler = new SessionHandler();
var maxIdleTime = application.getMaxSessionIdleTime();
int intMaxIdleSeconds;
if (maxIdleSeconds > Integer.MAX_VALUE) {
if (maxIdleTime > Integer.MAX_VALUE) {
log.warn("Max session idle time value is greater than Integer.MAX_VALUE. Integer.MAX_VALUE will be used instead");
intMaxIdleSeconds = Integer.MAX_VALUE;
} else {
intMaxIdleSeconds = (int) maxIdleSeconds;
maxIdleTime = Integer.MAX_VALUE;
}
intMaxIdleSeconds = (int) (maxIdleTime / 1000);
log.debug("Max http session idle time: " + intMaxIdleSeconds + "s");
sessionHandler.setMaxInactiveInterval(intMaxIdleSeconds);
DefaultSessionCache sessionCache = new DefaultSessionCache(sessionHandler);
FileSessionDataStore sessionStore = new FileSessionDataStore();
sessionStore.setStoreDir(sessionCacheFolder.toFile());
sessionCache.setSessionDataStore(sessionStore);
sessionHandler.setSessionCache(sessionCache);
@@ -29,6 +29,7 @@ import io.cloudbeaver.registry.WebServletHandlerDescriptor;
import io.cloudbeaver.server.CBAppConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.server.CBServerConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
@@ -212,9 +213,10 @@ public class CBStaticServlet extends DefaultServlet {
IOUtils.copyStream(fis, baos);
}
String indexContents = baos.toString(StandardCharsets.UTF_8);
CBServerConfig serverConfig = CBApplication.getInstance().getServerConfiguration();
indexContents = indexContents
.replace("{ROOT_URI}", CBApplication.getInstance().getRootURI())
.replace("{STATIC_CONTENT}", CBApplication.getInstance().getStaticContent());
.replace("{ROOT_URI}", serverConfig.getRootURI())
.replace("{STATIC_CONTENT}", serverConfig.getStaticContent());
byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
putHeaders(response, content, indexBytes.length);
@@ -18,6 +18,7 @@ package io.cloudbeaver.server.websockets;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WriteCallback;
@@ -76,6 +77,12 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
this.webSession.getEventsFilter().setSubscribedProjects(projectEvent.getProjectIds());
break;
}
case SESSION_PING: {
if (webSession instanceof WebSession session) {
session.updateInfo(true);
}
break;
}
default:
var e = new DBWebException("Unknown websocket client event: " + clientEvent.getId());
log.error(e.getMessage(), e);
@@ -29,6 +29,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -54,6 +55,7 @@ public class CBJettyWebSocketManager implements JettyWebSocketCreator {
var httpRequest = request.getHttpServletRequest();
var webSession = webSessionManager.getOrRestoreSession(httpRequest);
if (webSession != null) {
webSession.updateSessionParameters(httpRequest);
// web client session
return createNewEventsWebSocket(webSession);
}
@@ -41,6 +41,9 @@ public interface DBWServiceCore extends DBWService {
@WebAction(authRequired = false)
WebServerConfig getServerConfig() throws DBWebException;
@WebAction(authRequired = false)
WebProductSettings getProductSettings(@NotNull WebSession webSession);
@WebAction
List<WebDatabaseDriverInfo> getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException;
@@ -94,6 +97,7 @@ public interface DBWServiceCore extends DBWService {
@WebAction(authRequired = false)
boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse servletResponse) throws DBWebException;
@Deprecated
@WebAction(authRequired = false)
WebSession updateSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response)
throws DBWebException;
@@ -53,6 +53,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
WebSessionManager sessionManager = platform.getSessionManager();
model.getQueryType()
.dataFetcher("serverConfig", env -> getService(env).getServerConfig())
.dataFetcher("productSettings", env -> getService(env).getProductSettings(getWebSession(env)))
.dataFetcher("driverList", env -> getService(env).getDriverList(getWebSession(env), env.getArgument("id")))
.dataFetcher("authModels", env -> getService(env).getAuthModels(getWebSession(env)))
@@ -62,6 +62,7 @@ import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry;
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
@@ -177,8 +178,7 @@ public class WebServiceCore implements DBWServiceCore {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
for (DBPDataSourceContainer ds : registry.getDataSources()) {
if (ds.isTemplate() &&
CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver()))
{
CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver())) {
result.add(new WebConnectionInfo(webSession, ds));
}
}
@@ -206,13 +206,13 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull DBPProject project
) {
return project.getDataSourceRegistry().getAllFolders().stream()
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
}
@Override
public String[] getSessionPermissions(@NotNull WebSession webSession) throws DBWebException {
if (CBApplication.getInstance().isConfigurationMode()) {
return new String[] {
return new String[]{
DBWConstants.PERMISSION_ADMIN
};
}
@@ -224,8 +224,8 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession webSession,
@Nullable String defaultLocale,
@NotNull HttpServletRequest servletRequest,
@NotNull HttpServletResponse servletResponse) throws DBWebException
{
@NotNull HttpServletResponse servletResponse
) throws DBWebException {
for (WebSessionHandlerDescriptor hd : WebHandlerRegistry.getInstance().getSessionHandlers()) {
try {
hd.getInstance().handleSessionOpen(webSession, servletRequest, servletResponse);
@@ -255,7 +255,11 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public List<WebServerMessage> readSessionLog(@NotNull WebSession webSession, Integer maxEntries, Boolean clearEntries) {
public List<WebServerMessage> readSessionLog(
@NotNull WebSession webSession,
Integer maxEntries,
Boolean clearEntries
) {
return webSession.readLog(maxEntries, clearEntries);
}
@@ -282,11 +286,13 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
@Deprecated
public boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
return CBPlatform.getInstance().getSessionManager().touchSession(request, response);
}
@Override
@Deprecated
public WebSession updateSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response)
throws DBWebException {
WebSessionManager sessionManager = CBPlatform.getInstance().getSessionManager();
@@ -295,7 +301,8 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response)
throws DBWebException {
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request, response);
if (session == null) {
return false;
@@ -370,10 +377,12 @@ public class WebServiceCore implements DBWServiceCore {
if (networkCredentials != null) {
networkCredentials.forEach(c -> {
if (CommonUtils.toBoolean(c.isSavePassword())) {
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration().getHandler(c.getId());
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration()
.getHandler(c.getId());
if (handlerCfg != null &&
// check username param only for ssh config
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(), DBWHandlerType.TUNNEL))
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(),
DBWHandlerType.TUNNEL))
) {
WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c);
handlerCfg.setSavePassword(true);
@@ -396,14 +405,16 @@ public class WebServiceCore implements DBWServiceCore {
if (project.isUseSecretStorage()) {
try {
dataSourceContainer.persistSecrets(
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
);
} catch (DBException e) {
throw new DBWebException("Failed to save credentials", e);
}
}
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo, dataSourceContainer, dataSourceContainer.getConnectionConfiguration());
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo,
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration());
saveConfig[0] = true;
}
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
@@ -438,7 +449,8 @@ public class WebServiceCore implements DBWServiceCore {
if (connectionConfig.isTemplate()) {
connectionConfig.setSaveCredentials(false);
}
DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, sessionRegistry);
DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig,
sessionRegistry);
if (CommonUtils.isEmpty(newDataSource.getName())) {
newDataSource.setName(CommonUtils.notNull(connectionConfig.getName(), "NewConnection"));
}
@@ -454,7 +466,8 @@ public class WebServiceCore implements DBWServiceCore {
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource);
webSession.addConnection(connectionInfo);
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(newDataSource));
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(
newDataSource));
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
@@ -469,7 +482,8 @@ public class WebServiceCore implements DBWServiceCore {
public WebConnectionInfo updateConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig config) throws DBWebException {
@NotNull WebConnectionConfig config
) throws DBWebException {
// Do not check for custom connection option. Already created connections can be edited.
// Also template connections can be edited
// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
@@ -492,7 +506,9 @@ public class WebServiceCore implements DBWServiceCore {
dataSource.setFolder(config.getFolder() != null ? sessionRegistry.getFolder(config.getFolder()) : null);
WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(), dataSource.getConnectionConfiguration(), config);
WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(),
dataSource.getConnectionConfiguration(),
config);
// we should check that the config has changed but not check for password changes
dataSource.setSharedCredentials(config.isSharedCredentials());
@@ -541,7 +557,10 @@ public class WebServiceCore implements DBWServiceCore {
return connectionInfo;
}
private WSDataSourceProperty getDatasourceEventProperty(DataSourceDescriptor oldDataSource, DBPDataSourceContainer dataSource) {
private WSDataSourceProperty getDatasourceEventProperty(
DataSourceDescriptor oldDataSource,
DBPDataSourceContainer dataSource
) {
if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) {
return WSDataSourceProperty.CONFIGURATION;
}
@@ -581,8 +600,8 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String templateId,
@Nullable String connectionName) throws DBWebException
{
@Nullable String connectionName
) throws DBWebException {
DBPDataSourceRegistry templateRegistry = getProjectById(webSession, projectId).getDataSourceRegistry();
DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId);
if (dataSourceTemplate == null) {
@@ -616,7 +635,8 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String nodePath,
@NotNull WebConnectionConfig config) throws DBWebException {
@NotNull WebConnectionConfig config
) throws DBWebException {
try {
DBNModel navigatorModel = webSession.getNavigatorModel();
DBPDataSourceRegistry dataSourceRegistry = getProjectById(webSession, projectId).getDataSourceRegistry();
@@ -628,7 +648,7 @@ public class WebServiceCore implements DBWServiceCore {
if (!(srcNode instanceof DBNDataSource)) {
throw new DBException("Node '" + nodePath + "' is not a datasource node");
}
DBPDataSourceContainer dataSourceTemplate = ((DBNDataSource)srcNode).getDataSourceContainer();
DBPDataSourceContainer dataSourceTemplate = ((DBNDataSource) srcNode).getDataSourceContainer();
DBPDataSourceContainer newDataSource = dataSourceRegistry.createDataSource(dataSourceTemplate);
@@ -716,7 +736,9 @@ public class WebServiceCore implements DBWServiceCore {
testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig,
sessionRegistry);
}
webSession.provideAuthParameters(webSession.getProgressMonitor(), testDataSource, testDataSource.getConnectionConfiguration());
webSession.provideAuthParameters(webSession.getProgressMonitor(),
testDataSource,
testDataSource.getConnectionConfiguration());
testDataSource.setSavePassword(true); // We need for test to avoid password callback
if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) {
((DataSourceDescriptor) testDataSource).setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
@@ -740,21 +762,26 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException {
public WebNetworkEndpointInfo testNetworkHandler(
@NotNull WebSession webSession,
@NotNull WebNetworkHandlerConfigInput nhConfig
) throws DBWebException {
DBRProgressMonitor monitor = webSession.getProgressMonitor();
monitor.beginTask("Instantiate SSH tunnel", 2);
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhConfig.getId());
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance()
.getDescriptor(nhConfig.getId());
if (handlerDescriptor == null) {
throw new DBWebException("Network handler '" + nhConfig.getId() + "' not found");
}
try {
DBWNetworkHandler handler = handlerDescriptor.createHandler(DBWNetworkHandler.class);
if (handler instanceof DBWTunnel) {
DBWTunnel tunnel = (DBWTunnel)handler;
DBWTunnel tunnel = (DBWTunnel) handler;
DBPConnectionConfiguration connectionConfig = new DBPConnectionConfiguration();
connectionConfig.setHostName(DBConstants.HOST_LOCALHOST);
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties().get(DBWHandlerConfiguration.PROP_PORT)));
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties()
.get(DBWHandlerConfiguration.PROP_PORT)));
try {
monitor.subTask("Initialize tunnel");
@@ -858,7 +885,9 @@ public class WebServiceCore implements DBWServiceCore {
}
WebProjectImpl project = getProjectById(session, projectId);
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode, folderName, sessionRegistry);
DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode,
folderName,
sessionRegistry);
WebConnectionFolderInfo folderInfo = new WebConnectionFolderInfo(session, newFolder);
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
WebEventUtils.addNavigatorNodeUpdatedEvent(
@@ -934,7 +963,7 @@ public class WebServiceCore implements DBWServiceCore {
WebSession webSession, @Nullable String projectId, String id, DBNBrowseSettings settings
) throws DBWebException {
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(projectId, id);
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor)connectionInfo.getDataSourceContainer());
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer());
dataSourceDescriptor.setNavigatorSettings(settings);
dataSourceDescriptor.persistConfiguration();
WebEventUtils.addDataSourceUpdatedEvent(
@@ -947,7 +976,8 @@ public class WebServiceCore implements DBWServiceCore {
}
@Override
public WebAsyncTaskInfo getAsyncTaskInfo(WebSession webSession, String taskId, Boolean removeOnFinish) throws DBWebException {
public WebAsyncTaskInfo getAsyncTaskInfo(WebSession webSession, String taskId, Boolean removeOnFinish)
throws DBWebException {
return webSession.asyncTaskStatus(taskId, CommonUtils.toBoolean(removeOnFinish));
}
@@ -956,6 +986,11 @@ public class WebServiceCore implements DBWServiceCore {
return webSession.asyncTaskCancel(taskId);
}
@Override
public WebProductSettings getProductSettings(@NotNull WebSession webSession) {
return new WebProductSettings(webSession, ProductSettingsRegistry.getInstance().getSettings());
}
private WebProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException {
WebProjectImpl project = webSession.getProjectById(projectId);
if (project == null) {
@@ -79,10 +79,12 @@ public class WebSessionManager {
return application;
}
@Deprecated
public boolean touchSession(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response) throws DBWebException {
WebSession webSession = getWebSession(request, response, false);
webSession.updateInfo(request, response);
webSession.updateSessionParameters(request);
webSession.updateInfo(!request.getSession().isNew());
return true;
}
@@ -105,14 +107,14 @@ public class WebSessionManager {
var baseWebSession = sessionMap.get(sessionId);
if (baseWebSession == null && CBApplication.getInstance().isConfigurationMode()) {
try {
webSession = createWebSessionImpl(httpSession);
webSession = createWebSessionImpl(request);
} catch (DBException e) {
throw new DBWebException("Failed to create web session", e);
}
sessionMap.put(sessionId, webSession);
} else if (baseWebSession == null) {
try {
webSession = createWebSessionImpl(httpSession);
webSession = createWebSessionImpl(request);
} catch (DBException e) {
throw new DBWebException("Failed to create web session", e);
}
@@ -174,7 +176,7 @@ public class WebSessionManager {
return null;
}
webSession = createWebSessionImpl(httpSession);
webSession = createWebSessionImpl(request);
restorePreviousUserSession(webSession, oldAuthInfo);
sessionMap.put(sessionId, webSession);
@@ -208,8 +210,8 @@ public class WebSessionManager {
}
@NotNull
protected WebSession createWebSessionImpl(@NotNull HttpSession httpSession) throws DBException {
return new WebSession(httpSession, application, getSessionHandlers());
protected WebSession createWebSessionImpl(@NotNull HttpServletRequest request) throws DBException {
return new WebSession(request, application, getSessionHandlers());
}
@NotNull
@@ -330,7 +332,13 @@ public class WebSessionManager {
})
.forEach(session -> {
try {
session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid()));
session.addSessionEvent(new WSSessionStateEvent(
session.getLastAccessTimeMillis(),
session.getRemainingTime(),
session.isValid(),
((WebSession) session).isCacheExpired(),
((WebSession) session).getLocale(),
((WebSession) session).getActionParameters()));
} catch (Exception e) {
log.error("Failed to refresh session state: " + session.getSessionId(), e);
}
@@ -16,11 +16,13 @@
*/
package io.cloudbeaver.service.sql;
import io.cloudbeaver.server.CBPlatform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.sql.SQLModelPreferences;
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionContext;
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase;
@@ -66,7 +68,9 @@ public class WebSQLCompletionContext implements SQLCompletionContext {
@Override
public boolean isUseFQNames() {
return false;
return CBPlatform.getInstance()
.getPreferenceStore()
.getBoolean(SQLModelPreferences.SQL_EDITOR_PROPOSAL_ALWAYS_FQ);
}
@Override
@@ -364,9 +364,16 @@ public class WebSQLProcessor implements WebSessionProvider {
isAutoCommitEnabled = txnManager.isAutoCommit();
if (txnManager.isSupportsTransactions() && isAutoCommitEnabled) {
txnManager.setAutoCommit(monitor, false);
savepoint = txnManager.setSavepoint(monitor, null);
revertToAutoCommit = true;
}
if (!txnManager.isAutoCommit() && txnManager.supportsSavepoints()) {
try {
savepoint = txnManager.setSavepoint(monitor, null);
} catch (Throwable e) {
// May be savepoints not supported
log.debug("Can't set savepoint", e);
}
}
}
try {
Map<String, Object> options = Collections.emptyMap();
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.93.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.94.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.93-SNAPSHOT</version>
<version>1.0.94-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.93.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.94.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.93-SNAPSHOT</version>
<version>1.0.94-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -163,9 +163,12 @@ extend type Query {
}
extend type Mutation {
# Set user config parameter. If parameter value is null then removes the parameter
setUserConfigurationParameter(name: String!, value: Object): Boolean!
# Updates user preferences
setUserPreferences(preferences: Object!): UserInfo! @since(version: "24.0.1")
}
@@ -69,4 +69,10 @@ public interface DBWServiceAuth extends DBWService {
@NotNull String name,
@Nullable String value) throws DBWebException;
@WebAction()
WebUserInfo setUserConfigurationParameters(
@NotNull WebSession webSession,
@NotNull Map<String, Object> parameters
) throws DBWebException;
}
@@ -73,6 +73,9 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
env -> getService(env).setUserConfigurationParameter(getWebSession(env),
env.getArgument("name"),
env.getArgument("value")))
.dataFetcher("setUserPreferences",
env -> getService(env).setUserConfigurationParameters(getWebSession(env),
env.getArgument("preferences")))
;
}
}
@@ -18,7 +18,6 @@ package io.cloudbeaver.service.auth.impl;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
@@ -249,14 +248,40 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@NotNull WebSession webSession,
@NotNull String name,
@Nullable String value
) throws DBWebException {
if (webSession.getUser() == null) {
throw new DBWebException("Preferences cannot be changed for anonymous user");
}
return setPreference(webSession, name, value);
}
private static boolean setPreference(
@NotNull WebSession webSession,
@NotNull String name,
@Nullable Object value
) throws DBWebException {
webSession.addInfoMessage("Set user parameter - " + name);
try {
webSession.getSecurityController().setCurrentUserParameter(name, value);
String serializedValue = value == null ? null : value.toString();
webSession.getSecurityController().setCurrentUserParameter(name, serializedValue);
return true;
} catch (DBException e) {
throw new DBWebException("Error setting user parameter", e);
}
}
@Override
public WebUserInfo setUserConfigurationParameters(
@NotNull WebSession webSession,
@NotNull Map<String, Object> parameters
) throws DBWebException {
if (webSession.getUser() == null) {
throw new DBWebException("Preferences cannot be changed for anonymous user");
}
for (Map.Entry<String, Object> parameter : parameters.entrySet()) {
setPreference(webSession, parameter.getKey(), parameter.getValue());
}
return new WebUserInfo(webSession, webSession.getUser());
}
}
@@ -31,6 +31,7 @@ import java.io.IOException;
public class LocalServletHandler extends AbstractActionServletHandler {
public static final String URI_PREFIX = "open";
public static final String PARAM_PROJECT_ID = "project_id";
public static final String PARAM_CONNECTION_ID = "id";
public static final String PARAM_CONNECTION_NAME = "name";
public static final String PARAM_CONNECTION_URL = "url";
@@ -25,9 +25,7 @@ import io.cloudbeaver.server.actions.CBServerAction;
import org.jkiss.dbeaver.DBException;
import java.io.IOException;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* LocalSessionHandler
@@ -52,29 +50,22 @@ public class LocalSessionHandler extends AbstractActionSessionHandler {
@Override
protected void openDatabaseConsole(WebSession webSession, CBServerAction action) throws DBException {
String projectId = action.getParameter(LocalServletHandler.PARAM_PROJECT_ID);
String connectionId = action.getParameter(LocalServletHandler.PARAM_CONNECTION_ID);
String connectionName = action.getParameter(LocalServletHandler.PARAM_CONNECTION_NAME);
String connectionURL = action.getParameter(LocalServletHandler.PARAM_CONNECTION_URL);
WebConnectionInfo connectionInfo = null;
Stream<WebConnectionInfo> stream = webSession.getConnections().stream();
if (projectId != null) {
stream = stream.filter(c -> c.getProjectId().equals(projectId));
}
if (connectionId != null) {
connectionInfo = webSession.findWebConnectionInfo(connectionId);
stream = stream.filter(c -> c.getId().equals(connectionId));
} else if (connectionName != null) {
connectionInfo = findConnection(webSession, t -> t.getName().equals(connectionName));
stream = stream.filter(t -> t.getName().equals(connectionName));
} else if (connectionURL != null) {
connectionInfo = findConnection(webSession, t -> t.getUrl().equals(connectionURL));
}
if (connectionInfo == null) {
throw new DBException("Connection is not found in the session");
stream = stream.filter(t -> t.getUrl().equals(connectionURL));
}
WebConnectionInfo connectionInfo = stream.findFirst().orElseThrow(() -> new DBException("Connection is not found in the session"));
WebServiceUtils.fireActionParametersOpenEditor(webSession, connectionInfo.getDataSourceContainer(), false);
}
private WebConnectionInfo findConnection(WebSession webSession, Predicate<WebConnectionInfo> filter) {
List<WebConnectionInfo> filteredConnections = webSession.getConnections().stream().filter(filter).collect(Collectors.toList());
if (filteredConnections.size() != 1) {
return null;
}
return filteredConnections.get(0);
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.94.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.95.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.94-SNAPSHOT</version>
<version>1.0.95-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -53,6 +53,9 @@ extend type Query {
# Available transfer processors
dataTransferAvailableStreamProcessors: [ DataTransferProcessorInfo! ]!
# Available transfer processors for import file
dataTransferAvailableImportStreamProcessors: [ DataTransferProcessorInfo! ]!
dataTransferDefaultExportSettings: DataTransferDefaultExportSettings!
dataTransferExportDataFromContainer(
@@ -26,7 +26,10 @@ import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferStreamProcessor;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import io.cloudbeaver.service.sql.WebSQLProcessor;
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
import org.jkiss.code.NotNull;
import java.nio.file.Path;
import java.util.List;
/**
@@ -37,12 +40,22 @@ public interface DBWServiceDataTransfer extends DBWService {
@WebAction
List<WebDataTransferStreamProcessor> getAvailableStreamProcessors(WebSession session) throws DBWebException;
@WebAction
List<WebDataTransferStreamProcessor> getAvailableImportStreamProcessors(WebSession session) throws DBWebException;
@WebAction
WebAsyncTaskInfo dataTransferExportDataFromContainer(
WebSQLProcessor sqlProcessor,
String containerNodePath,
WebDataTransferParameters parameters) throws DBWebException;
@WebAction
WebAsyncTaskInfo asyncImportDataContainer(
@NotNull String processorId,
@NotNull Path path,
@NotNull WebSQLResultsInfo webSQLResultsInfo,
@NotNull WebSession webSession) throws DBWebException;
@WebAction
WebAsyncTaskInfo dataTransferExportDataFromResults(
WebSQLContextInfo sqlContextInfo,
@@ -24,6 +24,7 @@ import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferServlet;
import io.cloudbeaver.service.data.transfer.impl.WebServiceDataTransfer;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferImportServlet;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import org.jkiss.dbeaver.DBException;
@@ -42,6 +43,8 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
model.getQueryType()
.dataFetcher("dataTransferAvailableStreamProcessors",
env -> getService(env).getAvailableStreamProcessors(getWebSession(env)))
.dataFetcher("dataTransferAvailableImportStreamProcessors",
env -> getService(env).getAvailableImportStreamProcessors(getWebSession(env)))
.dataFetcher("dataTransferExportDataFromContainer", env -> getService(env).dataTransferExportDataFromContainer(
WebServiceBindingSQL.getSQLProcessor(env),
env.getArgument("containerNodePath"),
@@ -68,5 +71,10 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
new WebDataTransferServlet(application, getServiceImpl()),
application.getServicesURI() + "data/*"
);
servletContext.addServlet(
"dataTransferImport",
new WebDataTransferImportServlet(application, getServiceImpl()),
application.getServicesURI() + "data/import/*"
);
}
}
@@ -0,0 +1,123 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.data.transfer.impl;
import com.google.gson.stream.JsonWriter;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.WebServiceServletBase;
import io.cloudbeaver.service.data.transfer.DBWServiceDataTransfer;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import io.cloudbeaver.service.sql.WebSQLProcessor;
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@MultipartConfig
public class WebDataTransferImportServlet extends WebServiceServletBase {
private static final Log log = Log.getLog(WebDataTransferImportServlet.class);
public static final String ECLIPSE_JETTY_MULTIPART_CONFIG = "org.eclipse.jetty.multipartConfig";
DBWServiceDataTransfer dbwServiceDataTransfer;
public WebDataTransferImportServlet(CBApplication application, DBWServiceDataTransfer dbwServiceDataTransfer) {
super(application);
this.dbwServiceDataTransfer = dbwServiceDataTransfer;
}
@Override
protected void processServiceRequest(
WebSession session,
HttpServletRequest request,
HttpServletResponse response
) throws IOException, DBWebException {
if (!session.isAuthorizedInSecurityManager()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Import for users only");
return;
}
if ("POST".equalsIgnoreCase(request.getMethod())) {
Path tempFolder = CBPlatform.getInstance().getTempFolder(session.getProgressMonitor(), CBPlatform.TEMP_FILE_IMPORT_FOLDER);
MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(tempFolder.toString());
request.setAttribute(ECLIPSE_JETTY_MULTIPART_CONFIG, MULTI_PART_CONFIG);
Map<String, Object> variables = getVariables(request);
String projectId = JSONUtils.getString(variables, "projectId");
String connectionId = JSONUtils.getString(variables, "connectionId");
String contextId = JSONUtils.getString(variables, "contextId");
String resultId = JSONUtils.getString(variables, "resultsId");
String processorId = JSONUtils.getString(variables, "processorId");
if (projectId == null || connectionId == null || contextId == null || resultId == null || processorId == null) {
throw new IllegalArgumentException("Missing required parameters");
}
WebConnectionInfo webConnectionInfo = session.getWebConnectionInfo(projectId, connectionId);
WebSQLProcessor processor = WebServiceBindingSQL.getSQLProcessor(webConnectionInfo);
WebSQLContextInfo webSQLContextInfo = processor.getContext(contextId);
if (webSQLContextInfo == null) {
throw new DBWebException("Context is empty");
}
WebSQLResultsInfo webSQLResultsInfo = webSQLContextInfo.getResults(resultId);
Path filePath;
try {
InputStream file = request.getPart("fileData").getInputStream();
filePath = tempFolder.resolve(UUID.randomUUID().toString());
Files.write(filePath, file.readAllBytes());
} catch (ServletException e) {
throw new DBWebException(e.getMessage());
}
WebAsyncTaskInfo asyncImportDataContainer =
dbwServiceDataTransfer.asyncImportDataContainer(processorId, filePath, webSQLResultsInfo, session);
response.setContentType(CBConstants.APPLICATION_JSON);
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("id", asyncImportDataContainer.getId());
parameters.put("name", asyncImportDataContainer.getName());
parameters.put("running", asyncImportDataContainer.isRunning());
parameters.put("status", asyncImportDataContainer.getStatus());
parameters.put("error", asyncImportDataContainer.getError());
parameters.put("taskResult", asyncImportDataContainer.getTaskResult());
try (JsonWriter writer = new JsonWriter(response.getWriter())) {
JSONUtils.serializeMap(writer, parameters);
}
}
}
}
@@ -37,24 +37,27 @@ import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.sql.DBSQLException;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSDataManipulator;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
import org.jkiss.dbeaver.tools.transfer.IDataTransferConsumer;
import org.jkiss.dbeaver.tools.transfer.IDataTransferProcessor;
import org.jkiss.dbeaver.tools.transfer.database.DatabaseProducerSettings;
import org.jkiss.dbeaver.tools.transfer.database.DatabaseTransferProducer;
import org.jkiss.dbeaver.tools.transfer.database.*;
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor;
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferRegistry;
import org.jkiss.dbeaver.tools.transfer.stream.IStreamDataExporter;
import org.jkiss.dbeaver.tools.transfer.stream.StreamConsumerSettings;
import org.jkiss.dbeaver.tools.transfer.stream.StreamTransferConsumer;
import org.jkiss.dbeaver.tools.transfer.stream.*;
import org.jkiss.dbeaver.utils.ContentUtils;
import org.jkiss.utils.CommonUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.BatchUpdateException;
import java.util.*;
import java.util.stream.Collectors;
@@ -82,7 +85,19 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
@Override
public List<WebDataTransferStreamProcessor> getAvailableStreamProcessors(WebSession session) {
List<DataTransferProcessorDescriptor> processors = DataTransferRegistry.getInstance().getAvailableProcessors(StreamTransferConsumer.class, DBSEntity.class);
List<DataTransferProcessorDescriptor> processors = DataTransferRegistry.getInstance()
.getAvailableProcessors(StreamTransferConsumer.class, DBSEntity.class);
if (CommonUtils.isEmpty(processors)) {
return Collections.emptyList();
}
return processors.stream().map(x -> new WebDataTransferStreamProcessor(session, x)).collect(Collectors.toList());
}
@Override
public List<WebDataTransferStreamProcessor> getAvailableImportStreamProcessors(WebSession session) {
List<DataTransferProcessorDescriptor> processors =
DataTransferRegistry.getInstance().getAvailableProcessors(StreamTransferProducer.class, DBSEntity.class);
if (CommonUtils.isEmpty(processors)) {
return Collections.emptyList();
}
@@ -205,6 +220,43 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
return sqlProcessor.getWebSession().createAndRunAsyncTask("Data export", runnable);
}
public WebAsyncTaskInfo asyncImportDataContainer(@NotNull String processorId,
@NotNull Path path,
@NotNull WebSQLResultsInfo sqlContext,
@NotNull WebSession webSession) throws DBWebException {
webSession.addInfoMessage("Import data");
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(processorId);
DBSDataContainer dataContainer = sqlContext.getDataContainer();
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
monitor.beginTask("Import data", 1);
try {
monitor.subTask("Import data using " + processor.getName());
try {
importData(monitor, processor, (DBSDataManipulator) dataContainer, path);
} catch (Exception e) {
if (e instanceof DBException) {
throw e;
}
throw new DBException("Error importing data", e);
}
} catch (Throwable e) {
throw new InvocationTargetException(e);
} finally {
try {
Files.deleteIfExists(path);
} catch (IOException e) {
log.error("Failed to delete file: " + e.getMessage(), e);
}
monitor.done();
}
}
};
return webSession.createAndRunAsyncTask("Data import", runnable);
}
private void exportData(
DBRProgressMonitor monitor,
DataTransferProcessorDescriptor processor,
@@ -282,4 +334,45 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
consumer.finishTransfer(monitor, false);
}
private void importData(
DBRProgressMonitor monitor,
DataTransferProcessorDescriptor processor,
@NotNull DBSDataManipulator dataContainer,
Path path) throws DBException
{
IDataTransferProcessor processorInstance = processor.getInstance();
if (dataContainer.getDataSource() != null) {
DatabaseTransferConsumer consumer = new DatabaseTransferConsumer(dataContainer);
DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings();
databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource());
consumer.setSettings(databaseConsumerSettings);
DatabaseMappingContainer databaseMappingContainer = new DatabaseMappingContainer(databaseConsumerSettings, dataContainer);
databaseMappingContainer.getAttributeMappings(monitor);
databaseMappingContainer.setTarget(dataContainer);
consumer.setContainerMapping(databaseMappingContainer);
StreamTransferProducer producer = new StreamTransferProducer(new StreamEntityMapping(path), processor);
StreamProducerSettings producerSettings = new StreamProducerSettings();
Map<String, Object> properties = new HashMap<>();
for (DBPPropertyDescriptor prop : processor.getProperties()) {
properties.put(prop.getId(), prop.getDefaultValue());
}
producerSettings.setProcessorProperties(properties);
producerSettings.updateProducerSettingsFromStream(
monitor,
producer,
processorInstance,
properties);
try {
producer.transferData(monitor, consumer, processorInstance, producerSettings, null);
} catch (DBException e) {
throw new DBWebException("Import failed cause: " + e.getMessage());
}
}
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.11.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.12.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.11-SNAPSHOT</version>
<version>1.0.12-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.97.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.98.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.97-SNAPSHOT</version>
<version>1.0.98-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
Bundle-Version: 1.0.11.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.12.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.11-SNAPSHOT</version>
<version>1.0.12-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.46.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.47.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.46-SNAPSHOT</version>
<version>1.0.47-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.49.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.49-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -249,63 +249,20 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
}
}
public void addAllUsersToDefaultTeam() throws DBCException {
if (application.isConfigurationMode()) {
return;
}
if (CommonUtils.isEmpty(application.getAppConfiguration().getDefaultUserTeam())) {
return;
}
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID \n" +
"FROM {table_prefix}CB_USER\n" +
"WHERE USER_ID NOT IN (\n" +
" SELECT USER_ID FROM {table_prefix}CB_USER_TEAM CUT WHERE CUT.TEAM_ID = ? \n" +
")")
)) {
dbStat.setString(1, application.getAppConfiguration().getDefaultUserTeam());
ResultSet dbResult = dbStat.executeQuery();
List<String> usersIds = new ArrayList<>();
while (dbResult.next()) {
String userId = dbResult.getString(1);
usersIds.add(userId);
}
if (usersIds.isEmpty()) {
return;
}
for (String usersId : usersIds) {
try (PreparedStatement insertStat = dbCon.prepareStatement(
database.normalizeTableNames("INSERT INTO {table_prefix}CB_USER_TEAM(USER_ID, TEAM_ID, GRANT_TIME, GRANTED_BY)" +
" VALUES(?,?,?,?)"))) {
insertStat.setString(1, usersId);
insertStat.setString(2, application.getAppConfiguration().getDefaultUserTeam());
insertStat.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
insertStat.setString(4, "CloudBeaver Application");
insertStat.executeUpdate();
}
}
}
} catch (SQLException e) {
throw new DBCException("Error while setting default user teams", e);
}
}
@NotNull
@Override
public SMTeam[] getUserTeams(String userId) throws DBException {
Map<String, SMTeam> teams = new LinkedHashMap<>();
try (Connection dbCon = database.openConnection()) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(
"SELECT R.*,S.IS_SECRET_STORAGE FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R, " +
"{table_prefix}CB_AUTH_SUBJECT S " +
"WHERE UR.USER_ID=? AND UR.TEAM_ID=R.TEAM_ID AND S.SUBJECT_ID=R.TEAM_ID"))
"WHERE UR.USER_ID=? AND UR.TEAM_ID = R.TEAM_ID " +
"AND S.SUBJECT_ID IN (R.TEAM_ID,?)"))
) {
dbStat.setString(1, userId);
dbStat.setString(2, defaultUserTeam);
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
var team = fetchTeam(dbResult);
@@ -364,14 +321,16 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
readSubjectMetas(dbCon, user);
// Teams
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT TEAM_ID FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"))
database.normalizeTableNames("SELECT TEAM_ID FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"))
) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
dbStat.setString(1, userId);
try (ResultSet dbResult = dbStat.executeQuery()) {
List<String> teamIDs = new ArrayList<>();
Set<String> teamIDs = new LinkedHashSet<>();
while (dbResult.next()) {
teamIDs.add(dbResult.getString(1));
}
teamIDs.add(defaultUserTeam);
user.setUserTeams(teamIDs.toArray(new String[0]));
}
}
@@ -975,21 +934,27 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@Override
public SMTeam[] readAllTeams() throws DBCException {
try (Connection dbCon = database.openConnection()) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
Map<String, SMTeam> teams = new LinkedHashMap<>();
try (Statement dbStat = dbCon.createStatement()) {
try (ResultSet dbResult = dbStat.executeQuery(
database.normalizeTableNames("SELECT T.*,S.IS_SECRET_STORAGE FROM {table_prefix}CB_TEAM T," +
"{table_prefix}CB_AUTH_SUBJECT S " +
"WHERE T.TEAM_ID=S.SUBJECT_ID ORDER BY TEAM_ID"))) {
String query = database.normalizeTableNames(
"SELECT T.*, S.IS_SECRET_STORAGE FROM {table_prefix}CB_TEAM T, " +
"{table_prefix}CB_AUTH_SUBJECT S " +
"WHERE T.TEAM_ID IN (S.SUBJECT_ID, ?) ORDER BY TEAM_ID");
try (PreparedStatement dbPreparedStatement = dbCon.prepareStatement(query)) {
dbPreparedStatement.setString(1, defaultUserTeam);
try (ResultSet dbResult = dbPreparedStatement.executeQuery()) {
while (dbResult.next()) {
SMTeam team = fetchTeam(dbResult);
teams.put(team.getTeamId(), team);
}
}
try (ResultSet dbResult = dbStat.executeQuery(
database.normalizeTableNames("SELECT SUBJECT_ID,PERMISSION_ID\n" +
"FROM {table_prefix}CB_AUTH_PERMISSIONS AP, {table_prefix}CB_TEAM R\n" +
"WHERE AP.SUBJECT_ID=R.TEAM_ID\n"))) {
}
query = database.normalizeTableNames("SELECT SUBJECT_ID,PERMISSION_ID\n" +
"FROM {table_prefix}CB_AUTH_PERMISSIONS AP, {table_prefix}CB_TEAM R\n" +
"WHERE AP.SUBJECT_ID IN (R.TEAM_ID,?)\n");
try (PreparedStatement dbPreparedStatement = dbCon.prepareStatement(query)) {
dbPreparedStatement.setString(1, defaultUserTeam);
try (ResultSet dbResult = dbPreparedStatement.executeQuery()) {
while (dbResult.next()) {
SMTeam team = teams.get(dbResult.getString(1));
if (team != null) {
@@ -1016,16 +981,29 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@Override
public String[] getTeamMembers(String teamId) throws DBCException {
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER_TEAM WHERE TEAM_ID=?"))) {
dbStat.setString(1, teamId);
List<String> subjects = new ArrayList<>();
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
subjects.add(dbResult.getString(1));
if (application.getAppConfiguration().getDefaultUserTeam().equals(teamId)) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER"))) {
List<String> subjects = new ArrayList<>();
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
subjects.add(dbResult.getString(1));
}
}
return subjects.toArray(new String[0]);
}
} else {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER_TEAM WHERE TEAM_ID=?"))) {
dbStat.setString(1, teamId);
List<String> subjects = new ArrayList<>();
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
subjects.add(dbResult.getString(1));
}
}
return subjects.toArray(new String[0]);
}
return subjects.toArray(new String[0]);
}
} catch (SQLException e) {
throw new DBCException("Error while reading team members", e);
@@ -1240,7 +1218,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames(
"SELECT DISTINCT AP.PERMISSION_ID FROM {table_prefix}CB_AUTH_PERMISSIONS AP, {table_prefix}CB_USER_TEAM UR\n" +
"WHERE UR.TEAM_ID=AP.SUBJECT_ID AND UR.USER_ID=?"
"WHERE UR.TEAM_ID = AP.SUBJECT_ID AND UR.USER_ID=?"
)
)) {
dbStat.setString(1, userId);
@@ -16,18 +16,13 @@
*/
package io.cloudbeaver.service.security;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.service.security.db.CBDatabase;
import io.cloudbeaver.service.security.db.CBDatabaseConfig;
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
import io.cloudbeaver.service.security.internal.ClearAuthAttemptInfoJob;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import java.util.Map;
/**
* Embedded Security Controller Factory
*/
@@ -43,23 +38,19 @@ public class EmbeddedSecurityControllerFactory<T extends WebAuthApplication> {
*/
public CBEmbeddedSecurityController createSecurityService(
T application,
Map<String, Object> databaseConfig,
WebDatabaseConfig databaseConfig,
SMCredentialsProvider credentialsProvider,
SMControllerConfiguration smConfig
) throws DBException {
boolean initDb = false;
if (DB_INSTANCE == null) {
synchronized (EmbeddedSecurityControllerFactory.class) {
if (DB_INSTANCE == null) {
initDatabase(application, databaseConfig);
initDb = true;
DB_INSTANCE = new CBDatabase(application, databaseConfig);
}
}
}
var securityController = createEmbeddedSecurityController(
application, DB_INSTANCE, credentialsProvider, smConfig
);
if (initDb) {
var securityController = createEmbeddedSecurityController(
application, DB_INSTANCE, credentialsProvider, smConfig
);
//FIXME circular dependency
DB_INSTANCE.setAdminSecurityController(securityController);
DB_INSTANCE.initialize();
@@ -67,21 +58,11 @@ public class EmbeddedSecurityControllerFactory<T extends WebAuthApplication> {
// delete expired auth info job in enterprise products
new ClearAuthAttemptInfoJob(securityController).schedule();
}
return securityController;
}
return securityController;
}
private synchronized void initDatabase(WebAuthApplication application, Map<String, Object> databaseConfig) {
CBDatabaseConfig databaseConfiguration = new CBDatabaseConfig();
InstanceCreator<CBDatabaseConfig> dbConfigCreator = type -> databaseConfiguration;
InstanceCreator<CBDatabaseConfig.Pool> dbPoolConfigCreator = type -> databaseConfiguration.getPool();
Gson gson = new GsonBuilder()
.registerTypeAdapter(CBDatabaseConfig.class, dbConfigCreator)
.registerTypeAdapter(CBDatabaseConfig.Pool.class, dbPoolConfigCreator)
.create();
gson.fromJson(gson.toJsonTree(databaseConfig), CBDatabaseConfig.class);
DB_INSTANCE = new CBDatabase(application, databaseConfiguration);
return createEmbeddedSecurityController(
application, DB_INSTANCE, credentialsProvider, smConfig
);
}
protected CBEmbeddedSecurityController createEmbeddedSecurityController(
@@ -82,7 +82,7 @@ public class CBDatabase {
private static final String V2_DB_NAME = "cb.h2v2.dat";
private final WebApplication application;
private final CBDatabaseConfig databaseConfiguration;
private final WebDatabaseConfig databaseConfiguration;
private PoolingDataSource<PoolableConnection> cbDataSource;
private transient volatile Connection exclusiveConnection;
@@ -90,7 +90,7 @@ public class CBDatabase {
private SMAdminController adminSecurityController;
private SQLDialect dialect;
public CBDatabase(WebApplication application, CBDatabaseConfig databaseConfiguration) {
public CBDatabase(WebApplication application, WebDatabaseConfig databaseConfiguration) {
this.application = application;
this.databaseConfiguration = databaseConfiguration;
}
@@ -22,7 +22,7 @@ import org.jkiss.dbeaver.registry.storage.InternalDatabaseConfig;
/**
* Database configuration
*/
public class CBDatabaseConfig implements InternalDatabaseConfig {
public class WebDatabaseConfig implements InternalDatabaseConfig {
private String driver;
private String url;
private String user;
@@ -16,7 +16,7 @@
*/
package io.cloudbeaver.service.security.internal.utils;
import io.cloudbeaver.service.security.db.CBDatabaseConfig;
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
import org.jkiss.code.Nullable;
import org.jkiss.utils.CommonUtils;
@@ -37,7 +37,7 @@ public class DBConfigurationUtils {
static final String PARAM_DB_POOL_MAX_CONNECTIONS_CONFIGURATION = "maxConnections";
static final String PARAM_DB_POOL_VALIDATION_QUERY_CONFIGURATION = "validationQuery";
public static Map<String, Object> databaseConfigToMap(@Nullable CBDatabaseConfig databaseConfiguration) {
public static Map<String, Object> databaseConfigToMap(@Nullable WebDatabaseConfig databaseConfiguration) {
Map<String, Object> res = new LinkedHashMap<>();
if (databaseConfiguration == null) {
return res;
@@ -69,12 +69,12 @@ public class DBConfigurationUtils {
return res;
}
public static Map<String, Object> poolDatabaseConfigToMap(@Nullable CBDatabaseConfig databaseConfiguration) {
public static Map<String, Object> poolDatabaseConfigToMap(@Nullable WebDatabaseConfig databaseConfiguration) {
Map<String, Object> res = new LinkedHashMap<>();
if (databaseConfiguration == null) {
return res;
}
CBDatabaseConfig.Pool pool = databaseConfiguration.getPool();
WebDatabaseConfig.Pool pool = databaseConfiguration.getPool();
if (pool == null) {
return res;
} else {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver SLF4j Binding
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
Bundle-Version: 1.0.9.qualifier
Bundle-Release-Date: 20240325
Bundle-Version: 1.0.10.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.10-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.5.0</version>
<version>0.6.0-patch2</version>
<classifier>all</classifier>
</dependency>
</dependencies>
Binary file not shown.
Binary file not shown.
+30 -7
View File
@@ -14,12 +14,35 @@
<deps.output.dir>h2_v2</deps.output.dir>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-legacy-jar</id>
<phase>validate</phase>
<configuration>
<outputDirectory>../../../deploy/drivers/h2_v2</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>lib</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</configuration>
<goals>
<goal>copy-resources</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+25
View File
@@ -0,0 +1,25 @@
<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.kyuubi</artifactId>
<version>1.0.0</version>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>drivers</artifactId>
<version>1.0.0</version>
<relativePath>../</relativePath>
</parent>
<properties>
<deps.output.dir>kyuubi</deps.output.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.kyuubi</groupId>
<artifactId>kyuubi-hive-jdbc-shaded</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
</project>
+1
View File
@@ -18,6 +18,7 @@
<module>h2</module>
<module>h2_v2</module>
<module>jaybird</module>
<module>kyuubi</module>
<module>mysql</module>
<module>mariadb</module>
<module>oracle</module>
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>io.trino</groupId>
<artifactId>trino-jdbc</artifactId>
<version>422</version>
<version>438</version>
</dependency>
</dependencies>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.117.qualifier"
version="1.0.118.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.117-SNAPSHOT</version>
<version>1.0.118-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="24.0.1.qualifier"
version="24.0.2.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>24.0.1-SNAPSHOT</version>
<version>24.0.2-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="24.0.1.qualifier"
version="24.0.2.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>24.0.1-SNAPSHOT</version>
<version>24.0.2-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.47.qualifier"
version="1.0.48.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">

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