Compare commits

...
57 Commits
Author SHA1 Message Date
serge-rider 623f4458d9 23.1.4 config 2023-08-02 22:02:35 +02:00
alex eaeec9f1be CB-3628 clean key errors in ResourceError on refresh (#1868) 2023-08-02 21:27:38 +03:00
AinurandDaria Marutkina dd501d0716 CB-2903 apply predefined access to data sources from config file (#1833)
* CB-2903 apply predefined access to data sources from config file

* CB-2903 get subjects from auth subject table

* CB-2903 filtered subjects fix

* CB-2903 use json instead of conf for permissions configuration

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-08-02 19:11:59 +03:00
alex 1d2e1cd823 CB-3656 add validation inside the save script dialog (#1847)
* CB-3656 add validation inside the save script dialog

* CB-3656 respect root folder
2023-08-02 17:35:23 +03:00
Ainur b73dbe684b CB-3539 script task fix for TM (#1851) 2023-08-02 15:58:33 +03:00
AinurandEvgeniaBzzz 9b3098d68f CB-3448 anonymous user get query history from session id (#1845)
* CB-3448 anonymous user get query history from session id

* CB-3448 notify session change in anonymous user auth

---------

Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-08-02 13:59:24 +03:00
Ainur 069218cb5e CB-2403 add icon for reverse proxy (#1864) 2023-08-02 13:48:07 +03:00
AinurandDaria Marutkina 29ec2e09b7 CB-3744 add name service handler (#1862)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-08-02 13:47:17 +03:00
5b077f3189 Cb 3667 navigator filters (#1863)
* CB-3667 Navigator filters model

* CB-3667 Navigator filters model + implementation

* CB-3667 Instructions

* CB-3668 add navigator filters plugin

* CB-3667 Input parameters fix

* CB-3668 add include & exclude tables

* CB-3667 Save filter settings

* CB-3667 register plugin

* CB-3667 Load filter settings

* CB-3668 rename plugin

* CB-3668 add css modules

* CB-3668 add dependencies

* CB-3668 review fixes

* CB-3668 hide delete button by default

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-08-01 17:00:31 +03:00
Dmitriy Osipov 73b5e8fc59 CB-3780 fix: group component classes order (#1866) 2023-07-31 18:35:35 +03:00
Dmitriy Osipovandmr-anton-t 1073335af5 CB-3586: migrate tags to css-modules (#1835)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-31 18:35:16 +03:00
Alexander SkoblikovandDaria Marutkina d5cdb8fb43 CB-3385 add first/lastname to rp auth (#1857)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-31 15:01:35 +03:00
Alexander SkoblikovandDaria Marutkina 752f913db0 CB-2580 aws open id provider (#1861)
* CB-2580 aws open id provider

* CB-2580 read team meta in getUserTeams

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-31 15:01:02 +03:00
0d665a6b7b CB-3727: load common fields of network handler by default (#1854)
Co-authored-by: Alexey <wrouds@gmail.com>
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-28 16:42:09 +03:00
Dmitriy OsipovandEvgeniaBzzz 84cbf143ae CB-3672 fix: possibility to remove and rename project in RM (#1859)
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-28 14:01:34 +03:00
Alexey f4a62bbca8 chore: configure graphql codegen & types (#1860) 2023-07-28 13:59:12 +03:00
Alexander Skoblikovandmr-anton-t d93d7748cf Cb 3738 exception when logout invalid update token (#1853)
* CB-3738 log all gql errors

* CB-3310 do not update token for child sessions

* CB-3738 skip headless session on refresh

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-28 13:20:47 +03:00
Dmitriy OsipovandEvgeniaBzzz d2f1f29e80 CB-3746 fix: merge view separator line color (#1855)
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-28 00:59:05 +03:00
AinurandDaria Marutkina b7add23754 CB-3686 remove rm resource path from events (#1824)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-27 18:17:25 +03:00
07fc1f88b7 CB-3616 add logs for local rm (#1839)
* CB-3616 add logs for local rm

* CB-3616 add info about project id in logs

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-27 18:16:37 +03:00
Ainur c79a9a8efa CB-3775 test ssh handler fix (#1852) 2023-07-27 18:15:37 +03:00
e785d98c9b CB-3731 update trino driver (#1841)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-27 18:14:25 +03:00
Tatiana Fokinaandmr-anton-t 45c201c465 Cb 3587 migrate snackbars to css modules (#1842)
* CB-3587 add css modules, remove reshadow styles from snackbars

* CB-3587 fix notification cross button style

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-27 17:34:39 +03:00
664864d478 CB-3640 gitignore settings (#1830)
* CB-3640 gitignore settings

* CB-3640 ignore backup files

---------

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>
2023-07-27 16:46:44 +03:00
3cdcb07646 CB-3268 feat: admin users pagination (#1821)
* CB-3268 feat: admin users pagination

* CB-3268 fix: code style

* CB-3268 feat: filter users by enabled state

* CB-3268 feat: limit users loading

* CB-3268 fix: use limit/offset pagination

* CB-3268 fix pagination api

* CB-3268 fix users query

* CB-3268 fix: users refresh

---------

Co-authored-by: Aleksandr Skoblikov <aleksandr.skoblikov@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-27 15:27:14 +03:00
Tatiana Fokinaandmr-anton-t e33eb9fd60 Cb 3584 migrate containers to css modules (#1834)
* CB-3584 migrate containers, migrate form controls to css modules

* CB-3584 remove old container styles, fix layout props, fix styles

* CB-3584: Fix properties table filter style

* CB-3584 fix styles

* CB-3584 fix styles

* CB-3584 fix sizing styles

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-27 13:24:47 +03:00
Alexey 8fba6107b2 CB-3740 fix: handle authentication errors (#1844)
* CB-3740 fix: handle authentication errors

* CB-3740 chore: add localization
2023-07-26 20:13:44 +03:00
Alexey f41effd655 chore: deprecate useController (#1850) 2023-07-26 18:48:03 +03:00
mayer 9b34a496dc dbeaver/dbeaver-devops#635 non root images (#1848)
* devops#635 openshift support

* devops#635 openshift support: subdir name append
2023-07-26 15:17:16 +03:00
Dmitriy OsipovandEvgeniaBzzz 93c9cdd23d CB-3684 rename options in connection menu (#1836)
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-26 14:34:52 +03:00
Dmitriy OsipovandEvgeniaBzzz c7e6cf9d95 CB-3476 feat: save text selection and cursor position for each tab (#1846)
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
2023-07-26 13:38:30 +03:00
Serge Rider 27b0180107 dbeaver/pro#1797 Eliminate redundant i18n files (#1843) 2023-07-25 14:52:50 +03:00
f073954a32 Cb 1768 ssl support (#1829)
* CB-1768 filter desktop network handlers

* CB-3692 add ssl tab

* CB-3692 use secure properties for the ssl

* CB-1768 save ssl credentials fix

* CB-1768 set secure properties fix

* CB-1768 set secure properties fix

* CB-3692 add  properties categories

* CB-1768 add password feature

* CB-3692 support ssl in auth form

* CB-1768 do not mutate proxy objects

* CB-3692 do not pass secure properties if there is no changes

* CB-3692 do not send secure properties for the ssh

* CB-3692 add comments

* CB-3692 disable autofill

* CB-3692 disable autofill on init connection

* CB-3692 convert observables to non observables

---------

Co-authored-by: Ainur <yagudin10@yandex.ru>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-24 17:49:16 +03:00
Dmitriy OsipovandAlexey e5ed5c8682 CB-3585 migrate itemList to css-modules (#1832)
Co-authored-by: Alexey <wrouds@gmail.com>
2023-07-24 16:37:19 +03:00
Daria Marutkina 90cd350267 Merge pull request #1840 from dbeaver/dariamarutkina-patch-1
CB_2313 chore: update README
2023-07-24 12:52:43 +02:00
Daria Marutkina 1360631ab4 CB_2313 chore: update README 2023-07-24 12:52:17 +02:00
dda35760eb CB-3612 provisioning api (#1804)
* CB-3612 provisioning api

* CB-3612 review fixes

* CB-3486 rename getters

* CB-3486 azure ad get users info

* CB-3612 add filter to gql

* CB-3612 get resource token only after calling gql func

* CB-3486 fix front errors

* CB-3486 add translation

---------

Co-authored-by: Ainur <yagudin10@yandex.ru>
Co-authored-by: Tatiana Fokina <tatiana.fokina@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-19 20:30:58 +03:00
1086151bcb CB-1652 add cross button to search fields (#1803)
* CB-1652 add cross button to search fields

* СB-1652 PR fixes

* CB-1652 PR fixes

* CB-1652 add cross button to driver properties filter

* CB-1652 fix typo

---------

Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-19 18:58:21 +03:00
Dmitry Osipovandmr-anton-t e7c066e592 CB-3720 fix: changed extensions order for editor (#1825)
* CB-3720 fix: changed extensions order for editor

* CB-3720 add priority for extensions

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-19 17:33:24 +03:00
Dmitry Osipovandmr-anton-t 5aae41d1fe CB-3725: fix: prop name for MenuBarSmallItem (#1828)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-19 15:32:53 +03:00
Dmitry Osipovandmr-anton-t 4ad921a9c8 CB-3721 fix: replaced MOD key with CTRL in Linux (#1826)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-19 14:40:04 +03:00
632c93f246 CB-3519 ability-to-unlink-sso-credentials-from-user (#1822)
* CB-3670 delete user credentials api

* CB-3669 add ability to remove user creds

* CB-3669 use modules

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-19 12:26:22 +03:00
Alexey 4e4b817a38 chore: update eclipse configs (#1827) 2023-07-19 12:07:20 +03:00
Serge Rider b097b1147a Extra Java opens 2023-07-18 17:56:32 +02:00
Serge Rider d00f08b9d0 23.1.3 config 2023-07-18 14:42:51 +02:00
Serge Rider 9d0ecce84f 23.1.3 config 2023-07-18 14:41:55 +02:00
7550915e9c CB-3544 clarify logs (#1820)
Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2023-07-18 14:34:33 +03:00
Alexeyanddariamarutkina 78c19187de Feat CB-3030 conflict resolving (#1814)
* CB-3030 feat: conflict resolving

* CB-3030 fix: display os specific key instead of mod

* CB-3030 fix: sql editor status

* CB-3030 fix: styles and data validation

* CB-3030 fix: sql editor styles

---------

Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-17 22:10:15 +03:00
Alexeyanddariamarutkina fc592908c2 CB-3680 fix: add suspense loader (#1823)
Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-16 20:31:43 +03:00
alexanddariamarutkina ba0cd2d3fa CB-3600 support files aggregation (#1813)
* CB-3600 support files aggregation

* CB-3600 notify about duplicates

---------

Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-14 15:15:34 +03:00
alexanddariamarutkina 872c226522 Cb 3675 projects notifications (#1818)
* CB-3675 add project notifications

* CB-3675 revert status message logic

---------

Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-14 15:15:04 +03:00
Alexeyanddariamarutkina ab94dd837b CB-3641 fix: search in visible node name (#1815)
Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-12 12:43:47 +03:00
alexandAlexey dce23485a7 CB-3549 fill connection config with defaults (#1806)
* CB-3549 fill connection config with defaults

* CB-3549 move mode check outside of the update name fn

---------

Co-authored-by: Alexey <wrouds@gmail.com>
2023-07-11 14:19:11 +03:00
Tatiana Fokinaanddariamarutkina 17510f2de1 CB-3608 memoize parse script (#1805)
Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
2023-07-10 14:55:24 +03:00
Serge Rider 98d171e503 dbeaver/pro#1766 legacy krb5 support (#1807)
* dbeaver/pro#1766 Legacy krb5 support

* 23.1.2 version bump
2023-07-10 11:19:26 +03:00
dariamarutkina 736d91fbe3 Merge pull request #1812 from dbeaver/dariamarutkina-patch-1
CB_2312 chore: update README
2023-07-10 10:00:47 +02:00
dariamarutkina c0b71a431a CB_2312 chore: update README 2023-07-10 10:00:12 +02:00
434 changed files with 8298 additions and 5855 deletions
+2 -1
View File
@@ -19,6 +19,7 @@
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5"
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode"
]
}
+14
View File
@@ -22,6 +22,20 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### CloudBeaver 23.1.3 - 2023-07-24
- Users can simultaneously edit resources, allowing them to work together;
- We have improved the UX in the search bar - users can delete a query or request by clicking on the cross icon;
- The search request considers file names and exclude the .sql file extension for now;
- Different bug fixes and enhancements have been made.
### CloudBeaver 23.1.2 - 2023-07-10
- We have improved the SQL Editor functionality by adding support for displaying tables with nested arrays of objects;
- The ability to compress files during export allows for faster download speeds, particularly for larger files;
- New Settings panel displays the product configuration settings such as Minimum fetch size, Maximum fetch size, and Default fetch size from the Data Editor;
- Different bug fixes and enhancements have been made.
### CloudBeaver 23.1.1 - 2023-06-26
- Connections are consistently displayed now when they are pre-configured into the workspace in the Global Configuration json file.
+1 -1
View File
@@ -3,6 +3,6 @@ FROM eclipse-temurin:17.0.4.1_1-jre-focal
COPY cloudbeaver /opt/cloudbeaver
EXPOSE 8978
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
WORKDIR /opt/cloudbeaver/
ENTRYPOINT ["./run-server.sh"]
+1 -1
View File
@@ -10,4 +10,4 @@ IF NOT EXIST workspace\.metadata (
)
)
java %JAVA_OPTS% --add-modules=ALL-SYSTEM --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED -jar %launcherJar% -product io.cloudbeaver.product.ce.product -web-config conf/cloudbeaver.conf -nl en -registryMultiLanguage
java %JAVA_OPTS% --add-modules=ALL-SYSTEM --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED --add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED -jar %launcherJar% -product io.cloudbeaver.product.ce.product -web-config conf/cloudbeaver.conf -nl en -registryMultiLanguage
+1 -1
View File
@@ -6,4 +6,4 @@ echo "Starting Cloudbeaver Server"
[ ! -d "workspace/.metadata" ] && mkdir -p workspace/.metadata && mkdir -p workspace/GlobalConfiguration/.dbeaver && [ ! -f "workspace/GlobalConfiguration/.dbeaver/data-sources.json" ] && cp conf/initial-data-sources.conf workspace/GlobalConfiguration/.dbeaver/data-sources.json
java ${JAVA_OPTS} --add-modules=ALL-SYSTEM --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED -jar ${launcherJar} -product io.cloudbeaver.product.ce.product -web-config conf/cloudbeaver.conf -nl en -registryMultiLanguage
java ${JAVA_OPTS} --add-modules=ALL-SYSTEM --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED --add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED -jar ${launcherJar} -product io.cloudbeaver.product.ce.product -web-config conf/cloudbeaver.conf -nl en -registryMultiLanguage
+2 -1
View File
@@ -2,6 +2,7 @@
<?pde version="3.8"?>
<target name="org.jkiss.tp.cloudbeaver">
<locations>
<location path="${env_var:DBEAVER_DEVEL_ECLIPSE_PATH}/plugins" type="Directory"/>
<location path="../eclipse/Eclipse.app/Contents/Eclipse/plugins" type="Directory"/>
<location path="../eclipse/plugins" type="Directory"/>
</locations>
</target>
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.32.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.34.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -22,6 +22,7 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.auth,
io.cloudbeaver.auth.provider,
io.cloudbeaver.auth.provider.local,
io.cloudbeaver.auth.provisioning,
io.cloudbeaver.websocket,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Model
@@ -3,5 +3,4 @@ output.. = target/classes/
bin.includes = .,\
META-INF/,\
plugin.xml,\
schema/,\
OSGI-INF/
schema/
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.32-SNAPSHOT</version>
<version>1.0.34-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -16,14 +16,15 @@
*/
package io.cloudbeaver;
import io.cloudbeaver.model.rm.RMUtils;
import org.eclipse.core.resources.IProject;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.registry.BaseProjectImpl;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
@@ -31,7 +32,7 @@ import org.jkiss.utils.Pair;
import java.nio.file.Path;
import java.util.Collection;
public class BaseWebProjectImpl extends BaseProjectImpl {
public class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
@NotNull
private final RMProject project;
@@ -33,9 +33,6 @@ public class DBWConstants {
public static final String STATE_ATTR_SIGN_IN_STATE = "state.signin";
public static final String USER_PROJECTS_FOLDER = "user-projects";
public static final String SHARED_PROJECTS_FOLDER = "shared-projects";
public enum SignInState {
GLOBAL,
EMBEDDED
@@ -0,0 +1,28 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import org.jkiss.dbeaver.model.DBPPage;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.util.Map;
public class WebPage extends DBPPage {
public WebPage(Map<String, Object> params) {
super(JSONUtils.getInteger(params, "offset"), JSONUtils.getInteger(params, "limit"));
}
}
@@ -26,6 +26,7 @@ import org.jkiss.dbeaver.model.DBPObject;
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import java.util.Map;
@@ -41,7 +42,7 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
@NotNull
default Map<String, Object> authExternalUser(
@NotNull DBRProgressMonitor monitor,
@NotNull Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
@Nullable SMAuthProviderCustomConfiguration providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
@NotNull Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
) throws DBException {
return authParameters;
@@ -51,7 +52,7 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
@NotNull
DBWUserIdentity getUserIdentity(
@NotNull DBRProgressMonitor monitor,
@NotNull Map<String, Object> providerConfig,
@Nullable SMAuthProviderCustomConfiguration customConfiguration,
@NotNull Map<String, Object> authParameters) throws DBException;
/**
@@ -0,0 +1,18 @@
package io.cloudbeaver.auth.provisioning;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.user.SMUserProvisioning;
import java.util.List;
public interface SMProvisioner {
@NotNull
List<SMUserProvisioning> listExternalUsers(
@NotNull WebSession webSession,
@NotNull SMAuthProviderCustomConfiguration customConfiguration,
@NotNull SMProvisioningFilter filter
) throws DBWebException;
}
@@ -0,0 +1,28 @@
package io.cloudbeaver.auth.provisioning;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.util.Map;
public class SMProvisioningFilter {
@Nullable
private final Integer offset;
@Nullable
private final Integer limit;
public SMProvisioningFilter(Map<String, Object> params) {
this.offset = JSONUtils.getInteger(params, "offset");
this.limit = JSONUtils.getInteger(params, "limit");
}
@Nullable
public Integer getOffset() {
return offset;
}
@Nullable
public Integer getLimit() {
return limit;
}
}
@@ -52,7 +52,7 @@ import java.util.stream.Collectors;
*/
public class WebConnectionInfo {
private static final String SECURED_VALUE = "********";
public static final String SECURED_VALUE = "********";
private final WebSession session;
private final DBPDataSourceContainer dataSourceContainer;
private WebServerError connectError;
@@ -22,6 +22,7 @@ import org.jkiss.dbeaver.model.net.DBWHandlerType;
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
import org.jkiss.utils.CommonUtils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -52,6 +53,7 @@ public class WebNetworkHandlerConfig {
return configuration.isEnabled();
}
@Deprecated
public SSHConstants.AuthType getAuthType() {
return CommonUtils.valueOf(SSHConstants.AuthType.class, configuration.getStringProperty(SSHConstants.PROP_AUTH_TYPE), SSHConstants.AuthType.PASSWORD);
}
@@ -64,7 +66,7 @@ public class WebNetworkHandlerConfig {
return CommonUtils.isEmpty(configuration.getPassword()) ? null : "";
}
@Deprecated // use secure properties
public String getKey() {
return CommonUtils.isEmpty(configuration.getSecureProperty(SSHConstants.PROP_KEY_VALUE)) ? null : "";
}
@@ -78,4 +80,13 @@ public class WebNetworkHandlerConfig {
return configuration.getProperties();
}
@NotNull
public Map<String, String> getSecureProperties() {
Map<String, String> secureProperties = new LinkedHashMap<>(configuration.getSecureProperties());
for (Map.Entry<String, String> property : secureProperties.entrySet()) {
property.setValue(CommonUtils.isEmpty(property.getValue()) ? null : WebConnectionInfo.SECURED_VALUE);
}
return secureProperties;
}
}
@@ -20,6 +20,7 @@ import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
import org.jkiss.utils.CommonUtils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -77,4 +78,16 @@ public class WebNetworkHandlerConfigInput {
return JSONUtils.getObjectOrNull(cfg, "properties");
}
public Map<String, String> getSecureProperties() {
var secureProperties = JSONUtils.getObjectOrNull(cfg, "secureProperties");
if (secureProperties == null) {
return null;
}
var result = new LinkedHashMap<String, String>();
for (Map.Entry<String, Object> property : secureProperties.entrySet()) {
result.put(property.getKey(), CommonUtils.toString(property.getValue()));
}
return result;
}
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model.app;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import java.util.Set;
@@ -33,5 +34,6 @@ public interface WebAuthConfiguration {
Set<SMAuthProviderCustomConfiguration> getAuthCustomConfigurations();
@Nullable
SMAuthProviderCustomConfiguration getAuthProviderConfiguration(String configId);
}
@@ -19,7 +19,7 @@ public abstract class DBNAbstractResourceManagerNode extends DBNNode implements
super(parentNode);
}
public void removeChildResourceNode(@NotNull Queue<RMResource> resourcePath) {
public void removeChildResourceNode(@NotNull Queue<String> resourcePath) {
if (children == null || resourcePath.isEmpty()) {
return;
}
@@ -49,7 +49,7 @@ public abstract class DBNAbstractResourceManagerNode extends DBNNode implements
return;
}
var expectedResource = resourcePath.poll();
var node = RMNavigatorUtils.findResourceNode(children, expectedResource);
var node = RMNavigatorUtils.findResourceNode(children, expectedResource.getName());
if (node == null) { // we are in expected parent node
DBNResourceManagerResource newResourceNode = new DBNResourceManagerResource(this, expectedResource);
children = ArrayUtils.add(DBNResourceManagerResource.class, children, newResourceNode);
@@ -101,7 +101,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
return children;
}
public RMController getResourceController() {
protected RMController getResourceController() {
return ((DBNResourceManagerRoot) getParentNode()).getResourceController();
}
@@ -20,6 +20,7 @@ package io.cloudbeaver.model.rm;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPHiddenObject;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -34,6 +35,8 @@ import java.util.*;
public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject, RMEventListener {
private static final Log log = Log.getLog(DBNResourceManagerRoot.class);
private DBNResourceManagerProject[] projects;
private RMController resourceController;
@@ -123,10 +126,10 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
var action = event.getAction();
switch (action) {
case RESOURCE_DELETE:
deleteResourceNode(event.getProject(), event.getResourceTree());
deleteResourceNode(event.getProject(), event.getResourcePath());
break;
case RESOURCE_ADD:
addResourceNode(event.getProject(), event.getResourceTree());
addResourceNode(event.getProject(), event.getResourcePath());
break;
case PROJECT_ADD:
addProjectNode(event.getProject());
@@ -134,9 +137,13 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
}
}
private void deleteResourceNode(RMProject project, List<RMResource> resourcePath) {
private void deleteResourceNode(RMProject project, String resourcePath) {
if (resourcePath == null) {
return;
}
var projectNode = getProjectNode(project);
projectNode.ifPresent(dbnResourceManagerProject -> dbnResourceManagerProject.removeChildResourceNode(new ArrayDeque<>(resourcePath)));
var rmResourcePath = Arrays.asList(resourcePath.split("/"));
projectNode.ifPresent(dbnResourceManagerProject -> dbnResourceManagerProject.removeChildResourceNode(new ArrayDeque<>(rmResourcePath)));
}
@NotNull
@@ -146,9 +153,17 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
.findFirst();
}
private void addResourceNode(RMProject project, List<RMResource> resourcePath) {
private void addResourceNode(RMProject project, String resourcePath) {
if (resourcePath == null) {
return;
}
var projectNode = getProjectNode(project);
projectNode.ifPresent(dbnResourceManagerProject -> dbnResourceManagerProject.addChildResourceNode(new ArrayDeque<>(resourcePath)));
try {
var rmResourcePath = Arrays.asList(getResourceController().getResourcePath(project.getId(), resourcePath));
projectNode.ifPresent(dbnResourceManagerProject -> dbnResourceManagerProject.addChildResourceNode(new ArrayDeque<>(rmResourcePath)));
} catch (DBException e) {
log.error("Cannot add new node to resource manager tree", e);
}
}
private void addProjectNode(RMProject project) {
@@ -3,11 +3,11 @@ package io.cloudbeaver.model.rm;
import org.jkiss.dbeaver.model.rm.RMResource;
class RMNavigatorUtils {
public static DBNResourceManagerResource findResourceNode(DBNResourceManagerResource[] resourceNodes, RMResource expectedResource) {
public static DBNResourceManagerResource findResourceNode(DBNResourceManagerResource[] resourceNodes, String expectedResource) {
DBNResourceManagerResource node = null;
for (DBNResourceManagerResource resourceNode : resourceNodes) {
var nodeResource = resourceNode.getResource();
if (nodeResource.getName().equals(expectedResource.getName()) && nodeResource.isFolder() == expectedResource.isFolder()) {
if (nodeResource.getName().equals(expectedResource)) {
node = resourceNode;
break;
}
@@ -1,103 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2023 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.rm;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.model.app.BaseWebApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
public class RMUtils {
public static Path getRootPath() {
return DBWorkbench.getPlatform().getWorkspace().getAbsolutePath();
}
public static Path getUserProjectsPath() {
return getRootPath().resolve(DBWConstants.USER_PROJECTS_FOLDER);
}
public static Path getSharedProjectsPath() {
return getRootPath().resolve(DBWConstants.SHARED_PROJECTS_FOLDER);
}
@NotNull
public static Path getProjectPath(RMProject project) {
switch (project.getType()) {
case GLOBAL:
return getRootPath().resolve(BaseWebApplication.getInstance().getDefaultProjectName());
case SHARED:
return getSharedProjectsPath().resolve(project.getName());
default:
return getUserProjectsPath().resolve(project.getName());
}
}
/**
* Different types of project have different location on the workspace. Method returns path to the project.
*
* @param projectId project name or other identifier
* @return path to the project based on the name of projects and prefixes that it contains
*/
@NotNull
public static Path getProjectPathById(@NotNull String projectId) throws DBException {
int divPos = projectId.indexOf("_");
if (divPos <= 0) {
throw new DBException("Bad project ID");
}
String prefix = projectId.substring(0, divPos);
String projectName = projectId.substring(divPos + 1);
switch (RMProjectType.getByPrefix(prefix)) {
case GLOBAL:
String defaultProjectName = BaseWebApplication.getInstance().getDefaultProjectName();
if (CommonUtils.isEmpty(defaultProjectName)) {
throw new DBException("Global projects are not supported");
}
return getRootPath().resolve(defaultProjectName);
case SHARED:
return getSharedProjectsPath().resolve(projectName);
default:
return getUserProjectsPath().resolve(projectName);
}
}
public static Set<String> parseProjectPermissions(Set<String> permissions) {
return permissions.stream()
.map(RMProjectPermission::fromPermission).filter(Objects::nonNull)
.flatMap(permission -> permission.getAllPermissions().stream())
.collect(Collectors.toSet());
}
public static RMProject createAnonymousProject() {
RMProject project = new RMProject("anonymous");
project.setId("anonymous");
project.setType(RMProjectType.USER);
project.setProjectPermissions(RMProjectPermission.DATA_SOURCES_EDIT.getAllPermissions().toArray(new String[0]));
return project;
}
}
@@ -19,7 +19,6 @@ package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.rm.RMUtils;
import io.cloudbeaver.model.rm.lock.RMFileLockController;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebSQLConstants;
@@ -74,7 +73,7 @@ public class LocalResourceController implements RMController {
public static final String DEFAULT_CHANGE_ID = "0";
private final DBPWorkspace workspace;
private final SMCredentialsProvider credentialsProvider;
protected final SMCredentialsProvider credentialsProvider;
private final Path rootPath;
private final Path userProjectsPath;
@@ -289,6 +288,7 @@ public class LocalResourceController implements RMController {
throw new DBException("Project '" + name + "' not created");
}
try {
log.debug("Creating project '" + project.getId() + "'");
Files.createDirectories(projectPath);
if (WebAppUtils.getWebApplication().isMultiNode()) {
createResourceTypeFolders(projectPath);
@@ -309,6 +309,7 @@ public class LocalResourceController implements RMController {
throw new DBException("Project '" + project.getName() + "' doesn't exists");
}
try {
log.debug("Deleting project '" + projectId + "'");
IOUtils.deleteDirectory(targetPath);
getSecurityController().deleteAllObjectPermissions(projectId, SMObjects.PROJECT);
} catch (IOException e) {
@@ -346,6 +347,7 @@ public class LocalResourceController implements RMController {
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating value for property '" + propName + "' in project '" + projectId + "'");
webProject.setProjectProperty(propName, propValue);
return null;
}
@@ -355,12 +357,12 @@ public class LocalResourceController implements RMController {
@Override
public String getProjectsDataSources(@NotNull String projectId, @Nullable String[] dataSourceIds) throws DBException {
DBPProject projectMetadata = getWebProject(projectId, false);
DBPDataSourceRegistry registry = projectMetadata.getDataSourceRegistry();
registry.checkForErrors();
return doFileReadOperation(
projectId,
projectMetadata.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = projectMetadata.getDataSourceRegistry();
registry.checkForErrors();
DataSourceConfigurationManagerBuffer buffer = new DataSourceConfigurationManagerBuffer();
Predicate<DBPDataSourceContainer> filter = null;
if (!ArrayUtils.isEmpty(dataSourceIds)) {
@@ -404,6 +406,7 @@ public class LocalResourceController implements RMController {
false
);
registry.checkForErrors();
log.debug("Save data sources configuration in project '" + projectId + "'");
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return configChanged;
@@ -423,6 +426,7 @@ public class LocalResourceController implements RMController {
DBPDataSourceContainer dataSource = registry.getDataSource(dataSourceId);
if (dataSource != null) {
log.debug("Deleting data source '" + dataSourceId + "' in project '" + projectId + "'");
registry.removeDataSource(dataSource);
} else {
log.warn("Could not find datasource " + dataSourceId + " for deletion");
@@ -439,6 +443,7 @@ public class LocalResourceController implements RMController {
@NotNull String folderPath) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -468,6 +473,7 @@ public class LocalResourceController implements RMController {
for (String folderPath : folderPaths) {
DBPDataSourceFolder folder = registry.getFolder(folderPath);
if (folder != null) {
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
registry.removeFolder(folder, dropContents);
} else {
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
@@ -488,6 +494,7 @@ public class LocalResourceController implements RMController {
) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -566,6 +573,7 @@ public class LocalResourceController implements RMController {
if (Files.exists(targetPath)) {
throw new DBException("Resource '" + resourcePath + "' already exists");
}
log.debug("Creating resource '" + resourcePath + "' in project '" + projectId + "'");
createFolder(targetPath.getParent());
doFileWriteOperation(projectId, targetPath, () -> {
try {
@@ -595,10 +603,10 @@ public class LocalResourceController implements RMController {
var normalizedOldResourcePath = CommonUtils.normalizeResourcePath(oldResourcePath);
var normalizedNewResourcePath = CommonUtils.normalizeResourcePath(newResourcePath);
if (log.isDebugEnabled()) {
log.debug("Moving resource from '" + normalizedOldResourcePath + "' to '" + normalizedNewResourcePath + "'");
log.debug("Moving resource from '" + normalizedOldResourcePath + "' to '" + normalizedNewResourcePath +
"' in project '" + projectId + "'") ;
}
Path oldTargetPath = getTargetPath(projectId, normalizedOldResourcePath);
List<RMResource> rmOldResourcePath = makeResourcePath(projectId, oldTargetPath, false);
doFileWriteOperation(projectId, oldTargetPath, () -> {
if (!Files.exists(oldTargetPath)) {
@@ -621,7 +629,7 @@ public class LocalResourceController implements RMController {
return null;
});
fireRmResourceDeleteEvent(projectId, rmOldResourcePath);
fireRmResourceDeleteEvent(projectId, normalizedOldResourcePath);
fireRmResourceAddEvent(projectId, normalizedNewResourcePath);
}
@@ -659,11 +667,10 @@ public class LocalResourceController implements RMController {
public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "deleteResource")) {
if (log.isDebugEnabled()) {
log.debug("Removing resource from '" + resourcePath + "'" + (recursive ? " recursive" : ""));
log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : ""));
}
validateResourcePath(resourcePath);
Path targetPath = getTargetPath(projectId, resourcePath);
List<RMResource> rmResourcePath = makeResourcePath(projectId, targetPath, recursive);
doFileWriteOperation(projectId, targetPath, () -> {
if (!Files.exists(targetPath)) {
throw new DBException("Resource '" + resourcePath + "' doesn't exists");
@@ -696,7 +703,7 @@ public class LocalResourceController implements RMController {
});
log.debug("Fire resource delete event");
fireRmResourceDeleteEvent(projectId, rmResourcePath);
fireRmResourceDeleteEvent(projectId, resourcePath);
}
}
@@ -762,6 +769,7 @@ public class LocalResourceController implements RMController {
doFileWriteOperation(projectId, targetPath, () -> {
createFolder(targetPath.getParent());
try {
log.debug("Writing data to resource '" + targetPath + " in project " + projectId + "'");
Files.write(targetPath, data);
} catch (IOException e) {
throw new DBException("Error writing resource '" + resourcePath + "'", e);
@@ -799,6 +807,7 @@ public class LocalResourceController implements RMController {
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource property '" + propertyName + "' in project '" + projectId + "'");
webProject.setResourceProperty(resourcePath, propertyName, propertyValue);
return null;
}
@@ -922,21 +931,21 @@ public class LocalResourceController implements RMController {
}
}
private <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException {
protected <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
fileHandler.projectOpened(projectId);
}
return operation.doOperation();
}
private <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
fileHandler.beforeFileRead(projectId, file);
}
return operation.doOperation();
}
private <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
fileHandler.beforeFileChange(projectId, file);
}
@@ -1056,11 +1065,11 @@ public class LocalResourceController implements RMController {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_ADD,
getProject(projectId, false, false),
Arrays.asList(getResourcePath(projectId, resourcePath)))
resourcePath)
);
}
private void fireRmResourceDeleteEvent(@NotNull String projectId, @NotNull List<RMResource> resourcePath) throws DBException {
private void fireRmResourceDeleteEvent(@NotNull String projectId, @NotNull String resourcePath) throws DBException {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_DELETE,
makeProjectFromId(projectId, false),
@@ -27,7 +27,6 @@ import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebServerMessage;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.model.rm.RMUtils;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.service.DBWSessionHandler;
import io.cloudbeaver.service.sql.WebSQLConstants;
@@ -56,6 +55,7 @@ import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.BaseProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -467,6 +467,7 @@ public class WebSession extends BaseWebSession
}
SMAuthInfo authInfo = getSecurityController().authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE);
updateSMSession(authInfo);
notifySessionAuthChange();
}
@NotNull
@@ -103,7 +103,6 @@ public class WebSessionAuthProcessor {
String userId = curUser.getUserId();
var securityController = webSession.getSecurityController();
Map<String, Object> providerConfig = Collections.emptyMap();
var newAuthInfos = new ArrayList<WebAuthInfo>();
for (Map.Entry<SMAuthConfigurationReference, Object> entry : authInfo.getAuthData().entrySet()) {
SMAuthConfigurationReference authConfiguration = entry.getKey();
@@ -138,7 +137,10 @@ public class WebSessionAuthProcessor {
}
DBWUserIdentity userIdentity = null;
var providerConfigId = authConfiguration.getAuthProviderConfigurationId();
var providerConfig = WebAppUtils.getWebAuthApplication()
.getAuthConfiguration()
.getAuthProviderConfiguration(providerConfigId);
if (authProviderExternal != null) {
try {
userIdentity =
@@ -17,7 +17,6 @@
package io.cloudbeaver.model.session;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.rm.RMUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPPlatform;
@@ -25,6 +24,7 @@ import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
@@ -92,7 +92,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
}
@Override
public DBPProject getProject(@NotNull String projectName) {
public WebProjectImpl getProject(@NotNull String projectName) {
for (WebProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
@@ -80,16 +80,21 @@ public class WebUserContext implements SMCredentialsProvider {
* refresh context state based on new token from security manager
*
* @param smAuthInfo - auth info from security manager
* @return - true if context changed
* @throws DBException - if user already authorized and new token come from another user
*/
public synchronized boolean refresh(SMAuthInfo smAuthInfo) throws DBException {
if (smAuthInfo.getAuthStatus() != SMAuthStatus.SUCCESS || smAuthInfo.getSmAccessToken() == null) {
throw new DBCException("Authorization did not complete successfully");
}
if (smAuthInfo.getAuthPermissions() == null) {
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
throw new DBCException("Required information about session permissions is missing");
}
return refresh(smAuthInfo.getSmAccessToken(), smAuthInfo.getSmRefreshToken(), smAuthInfo.getAuthPermissions());
boolean sessionChanged = !CommonUtils.equalObjects(smSessionId, smAuthInfo.getAuthPermissions().getSessionId());
if (smAuthInfo.getAuthStatus() != SMAuthStatus.SUCCESS || (sessionChanged && smAuthInfo.getSmAccessToken() == null)) {
throw new DBCException("Authorization did not complete successfully");
}
if (sessionChanged) {
return refresh(smAuthInfo.getSmAccessToken(), smAuthInfo.getSmRefreshToken(), smAuthInfo.getAuthPermissions());
}
return false;
}
public synchronized boolean refresh(
@@ -108,7 +113,7 @@ public class WebUserContext implements SMCredentialsProvider {
smAuthPermissions.getSessionId(),
smAuthPermissions.getPermissions()
);
this.refreshToken = smRefreshToken;
setRefreshToken(smRefreshToken);
setUserPermissions(smAuthPermissions.getPermissions());
this.securityController = application.createSecurityController(this);
this.adminSecurityController = application.getAdminSecurityController(this);
@@ -131,7 +136,7 @@ public class WebUserContext implements SMCredentialsProvider {
return;
}
var newTokens = securityController.refreshSession(refreshToken);
this.refreshToken = newTokens.getSmRefreshToken();
setRefreshToken(newTokens.getSmRefreshToken());
this.smCredentials = new SMCredentials(
newTokens.getSmAccessToken(),
smCredentials.getUserId(),
@@ -204,6 +209,7 @@ public class WebUserContext implements SMCredentialsProvider {
*/
public synchronized void refreshPermissions() throws DBException {
if (isAuthorizedInSecurityManager()) {
log.debug("refresh permissions " + getUserId() + " " + getSmSessionId());
setUserPermissions(securityController.getTokenPermissions().getPermissions());
} else {
setUserPermissions(getDefaultPermissions());
@@ -238,4 +244,8 @@ public class WebUserContext implements SMCredentialsProvider {
public Set<String> getAccessibleProjectIds() {
return accessibleProjectIds;
}
private void setRefreshToken(@Nullable String refreshToken) {
this.refreshToken = refreshToken;
}
}
@@ -54,6 +54,14 @@ public class WebAppUtils {
return (WebApplication) DBWorkbench.getPlatform().getApplication();
}
public static WebAuthApplication getWebAuthApplication() throws DBException {
WebApplication application = getWebApplication();
if (!WebAuthApplication.class.isAssignableFrom(application.getClass())) {
throw new DBException("The current application doesn't contain authorization configuration");
}
return (WebAuthApplication) application;
}
public static SMAuthenticationManager getAuthManager(WebApplication application) throws DBException {
var smController = application.createSecurityController(new NoAuthCredentialsProvider());
if (!SMAuthenticationManager.class.isAssignableFrom(smController.getClass())) {
@@ -144,12 +152,7 @@ public class WebAppUtils {
@NotNull
public static StringBuilder getAuthApiPrefix(String serviceId) throws DBException {
WebApplication application = getWebApplication();
if (!WebAuthApplication.class.isAssignableFrom(application.getClass())) {
throw new DBException("The current application doesn't contain authorization configuration");
}
WebAuthApplication webAuthApplication = (WebAuthApplication) application;
return getAuthApiPrefix(webAuthApplication, serviceId);
return getAuthApiPrefix(getWebAuthApplication(), serviceId);
}
@NotNull
@@ -88,11 +88,25 @@ public class WebDataSourceUtils {
if (cfgInput.getPassword() != null) {
handlerConfig.setPassword(cfgInput.getPassword());
}
if (cfgInput.getKey() != null) {
setSecureProperties(handlerConfig, cfgInput, true);
if (cfgInput.getKey() != null) { // backward compatibility
handlerConfig.setSecureProperty(SSHConstants.PROP_KEY_VALUE, cfgInput.getKey());
}
}
private static void setSecureProperties(DBWHandlerConfiguration handlerConfig, WebNetworkHandlerConfigInput cfgInput, boolean ignoreNulls) {
var secureProperties = cfgInput.getSecureProperties();
if (secureProperties == null) {
return;
}
for (var pr : secureProperties.entrySet()) {
if (ignoreNulls && pr.getValue() == null) {
continue;
}
handlerConfig.setSecureProperty(pr.getKey(), pr.getValue());
}
}
@Nullable
public static DBPDataSourceContainer getLocalOrGlobalDataSource(
WebApplication application, WebSession webSession, @Nullable String projectId, String connectionId
@@ -121,7 +135,7 @@ public class WebDataSourceUtils {
public static void updateHandlerCredentials(DBWHandlerConfiguration handlerCfg, WebNetworkHandlerConfigInput webConfig) {
handlerCfg.setUserName(webConfig.getUserName());
handlerCfg.setPassword(webConfig.getPassword());
handlerCfg.setSecureProperty(SSHConstants.PROP_KEY_VALUE, webConfig.getKey());
setSecureProperties(handlerCfg, webConfig, false);
}
@@ -130,18 +130,16 @@ public class WebEventUtils {
String projectId,
WebSession session,
String resourcePath,
RMResource[] resourceParsedPath,
WSConstants.EventAction eventAction,
WSResourceProperty property
) {
addRmResourceUpdatedEvent(projectId, session, resourcePath, resourceParsedPath, eventAction, property, null);
addRmResourceUpdatedEvent(projectId, session, resourcePath, eventAction, property, null);
}
public static void addRmResourceUpdatedEvent(
String projectId,
WebSession session,
String resourcePath,
RMResource[] resourceParsedPath,
WSConstants.EventAction eventAction,
WSResourceProperty property,
String details
@@ -154,7 +152,6 @@ public class WebEventUtils {
session.getUserId(),
projectId,
resourcePath,
resourceParsedPath,
property,
details
);
@@ -165,7 +162,6 @@ public class WebEventUtils {
session.getUserId(),
projectId,
resourcePath,
resourceParsedPath,
property,
details
);
@@ -176,7 +172,6 @@ public class WebEventUtils {
session.getUserId(),
projectId,
resourcePath,
resourceParsedPath,
property,
details
);
@@ -1,13 +1,12 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 23.1.2.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 23.1.4.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Require-Bundle: io.cloudbeaver.server
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.product.ce
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver CE bundle
@@ -2,5 +2,4 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>23.1.2-SNAPSHOT</version>
<version>23.1.4-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.77.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.79.qualifier
Bundle-Release-Date: 20230807
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.77-SNAPSHOT</version>
<version>1.0.79-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 23.1.2.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 23.1.4.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -34,5 +34,4 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.service.session,
io.cloudbeaver.service.sql
Import-Package: org.slf4j
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.server
@@ -1,3 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Server
@@ -2,7 +2,6 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
static/,\
plugin.xml
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>23.1.2-SNAPSHOT</version>
<version>23.1.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,6 +3,11 @@ scalar Object
# Date/Time
scalar DateTime
input PageInput {
limit: Int
offset: Int
}
type Query
type Mutation
@@ -282,15 +282,17 @@ type NetworkHandlerDescriptor {
properties: [ObjectPropertyInfo!]!
}
# SSH network handler config. Name without prefix only for backward compatibility
type NetworkHandlerConfig {
id: ID!
enabled: Boolean!
authType: NetworkHandlerAuthType!
authType: NetworkHandlerAuthType! @deprecated # use properties
userName: String
password: String
key: String
key: String @deprecated # use secured properties
savePassword: Boolean!
properties: Object!
secureProperties: Object!
}
# Connection instance
@@ -426,12 +428,13 @@ input NavigatorSettingsInput {
input NetworkHandlerConfigInput {
id: ID!
enabled: Boolean
authType: NetworkHandlerAuthType
authType: NetworkHandlerAuthType @deprecated # use properties
userName: String
password: String
key: String
key: String @deprecated # use secured properties
savePassword: Boolean
properties: Object
secureProperties: Object
}
# Configuration of particular connection. Used for new connection create. Includes auth info
@@ -67,7 +67,7 @@ type NavigatorNodeInfo {
# Node type
nodeType: String
# Can this property have child nodes?
hasChildren: Boolean
hasChildren: Boolean!
# Project id of the node
projectId: String
@@ -82,9 +82,18 @@ type NavigatorNodeInfo {
# If is different than properties. It doesn't perform any expensive operation and doesn't require authentication.
nodeDetails: [ ObjectPropertyInfo! ]
folder: Boolean
inline: Boolean
navigable: Boolean
folder: Boolean!
inline: Boolean!
navigable: Boolean!
filtered: Boolean!
# Reads node filter. Expensive invocation, read only when it is really needed
filter: NavigatorNodeFilter
}
type NavigatorNodeFilter {
include: [String!]
exclude: [String!]
}
type DatabaseCatalog {
@@ -134,6 +143,10 @@ extend type Mutation {
navDeleteNodes( nodePaths: [ID!]! ): Int
# Moves nodes with specified IDs to the connection folder
navMoveNodesToFolder( nodePaths: [ID!]!, folderPath: ID!): Boolean
navMoveNodesToFolder( nodePaths: [ID!]!, folderPath: ID!): Boolean!
# Sets filter for the folder node. If both include and exclude are null then filter is removed.
# Node must be refreshed after applying filters. Node children can be changed
navSetFolderFilter( nodePath: ID!, include: [String!], exclude: [String!]): Boolean!
}
@@ -213,6 +213,7 @@ public class WebDatabaseDriverInfo {
return new String[0];
}
return NetworkHandlerRegistry.getInstance().getDescriptors(driver).stream()
.filter(h -> !h.isDesktopHandler())
.map(NetworkHandlerDescriptor::getId).toArray(String[]::new);
}
@@ -17,7 +17,7 @@
package io.cloudbeaver.model.user;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.auth.provisioning.SMProvisioner;
import io.cloudbeaver.registry.WebAuthProviderConfiguration;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.server.CBApplication;
@@ -78,6 +78,10 @@ public class WebAuthProviderInfo {
return descriptor.isPrivate();
}
public boolean isSupportProvisioning() {
return descriptor.getInstance() instanceof SMProvisioner;
}
public List<WebAuthProviderConfiguration> getConfigurations() {
List<WebAuthProviderConfiguration> result = new ArrayList<>();
for (SMAuthProviderCustomConfiguration cfg : CBApplication.getInstance().getAppConfiguration().getAuthCustomConfigurations()) {
@@ -33,6 +33,7 @@ import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.security.SMControllerConfiguration;
import io.cloudbeaver.service.session.WebSessionManager;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.service.datalocation.Location;
import org.jkiss.code.NotNull;
@@ -50,6 +51,7 @@ import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMObjects;
import org.jkiss.dbeaver.model.security.user.SMObjectPermissions;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import org.jkiss.dbeaver.model.websocket.event.WSServerConfigurationChangedEvent;
import org.jkiss.dbeaver.registry.BaseApplicationImpl;
@@ -262,6 +264,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
}
final Runtime runtime = Runtime.getRuntime();
initializeAdditionalConfiguration();
Location instanceLoc = Platform.getInstanceLocation();
try {
@@ -337,8 +340,11 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
if (configurationMode) {
// Try to configure automatically
performAutoConfiguration(configPath.toFile().getParentFile());
} else if (appConfiguration.isGrantConnectionsAccessToAnonymousTeam() && !isMultiNode()) {
grantAnonymousAccessToConnections(appConfiguration, "auto-grant");
} else if (!isMultiNode()) {
if (appConfiguration.isGrantConnectionsAccessToAnonymousTeam()) {
grantAnonymousAccessToConnections(appConfiguration, CBConstants.ADMIN_AUTO_GRANT);
}
grantPermissionsToConnections();
}
if (enableSecurityManager) {
@@ -360,6 +366,10 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
return;
}
protected void initializeAdditionalConfiguration() {
}
/**
* Configures server automatically.
* Called on startup
@@ -665,6 +675,20 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
}
}
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(File configFile) throws DBException {
Map<String, Object> configProps = new LinkedHashMap<>();
if (configFile.exists()) {
@@ -702,7 +726,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
public Map<String, Object> readConfigurationFile(File configFile) throws DBException {
try (Reader reader = new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8)) {
return JSONUtils.parseMap(getGson(), reader);
} catch (IOException e) {
} catch (Exception e) {
throw new DBException("Error parsing server configuration", e);
}
}
@@ -873,6 +897,35 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
}
}
private void grantPermissionsToConnections() {
try {
var globalRegistry = WebDataSourceUtils.getGlobalDataSourceRegistry();
var permissionsConfiguration = readConnectionsPermissionsConfiguration(globalRegistry.getProject().getMetadataFolder(false));
if (permissionsConfiguration == null) {
return;
}
for (var entry : permissionsConfiguration.entrySet()) {
var dataSourceId = entry.getKey();
var ds = globalRegistry.getDataSource(dataSourceId);
if (ds == null) {
log.error("Connection " + dataSourceId + " is not found in project " + globalRegistry.getProject().getName());
}
List<String> permissions = JSONUtils.getStringList(permissionsConfiguration, dataSourceId);
var securityController = getSecurityController();
securityController.deleteAllObjectPermissions(dataSourceId, SMObjects.DATASOURCE);
securityController.setObjectPermissions(
Set.of(dataSourceId),
SMObjects.DATASOURCE,
new HashSet<>(permissions),
Set.of(SMConstants.DATA_SOURCE_ACCESS_PERMISSION),
CBConstants.ADMIN_AUTO_GRANT
);
}
} catch (DBException e) {
log.error("Error granting permissions to connections", e);
}
}
protected void saveRuntimeConfig(
String newServerName,
String newServerURL,
@@ -60,6 +60,7 @@ public class CBConstants {
public static final String DEFAULT_DRIVERS_LOCATION = DEFAULT_DEPLOY_LOCATION + "/drivers";
public static final String DEFAULT_WORKSPACE_LOCATION = DEFAULT_DEPLOY_LOCATION + "/workspace";
public static final String DEFAULT_PRODUCT_CONFIGURATION = "conf/product.conf";
public static final String DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION = "data-sources-permissions.json";
public static final String DEFAULT_ADMIN_NAME = "cbadmin";
public static final String DEFAULT_ADMIN_TEAM = "admin";
@@ -79,4 +80,5 @@ public class CBConstants {
public static final String APPLICATION_JSON = "application/json";
public static final String QUOTA_PROP_FILE_LIMIT = "dataExportFileSizeLimit";
public static final String ADMIN_AUTO_GRANT = "auto-grant";
}
@@ -43,35 +43,28 @@ public class WSRmResourceUpdatedEventHandlerImpl extends WSAbstractProjectEventH
@Override
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSResourceUpdatedEvent event) {
if (activeUserSession instanceof WebSession) {
var parsedResourcePath = event.getResourceParsedPath();
var resourceParsedPath = parsedResourcePath instanceof RMResource[]
? (RMResource[]) parsedResourcePath
: gson.fromJson(gson.toJson(parsedResourcePath), RMResource[].class);
var webSession = (WebSession) activeUserSession;
acceptChangesInNavigatorTree(
WSEventType.valueById(event.getId()),
resourceParsedPath,
event.getResourcePath(),
webSession.getProjectById(event.getProjectId())
);
}
activeUserSession.addSessionEvent(event);
}
private void acceptChangesInNavigatorTree(WSEventType eventType,
RMResource[] resourceParsedPath,
WebProjectImpl project) {
List<RMResource> rmResourcePath = Arrays.asList(resourceParsedPath);
private void acceptChangesInNavigatorTree(WSEventType eventType, String resourcePath, WebProjectImpl project) {
if (eventType == WSEventType.RM_RESOURCE_CREATED) {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_ADD,
project.getRmProject(),
rmResourcePath)
resourcePath)
);
} else if (eventType == WSEventType.RM_RESOURCE_DELETED) {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_DELETE,
project.getRmProject(),
rmResourcePath)
resourcePath)
);
}
}
@@ -23,7 +23,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
public class SessionStateJob extends PeriodicSystemJob {
private static final Log log = Log.getLog(SessionStateJob.class);
private static final int PERIOD_MS = 60_000; // once per 60 seconds
private static final int PERIOD_MS = 30_000; // once per 60 seconds
public SessionStateJob(@NotNull CBPlatform platform) {
super("Session state sender", platform, PERIOD_MS);
@@ -174,6 +174,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
throw e.getTargetException();
}
} catch (Throwable ex) {
log.error("Unexpected error during gql request", ex);
if (SMUtils.isTokenExpiredExceptionWasHandled(ex)) {
WebSession webSession = findWebSession(env);
if (webSession != null) {
@@ -49,6 +49,13 @@ public interface DBWServiceNavigator extends DBWService {
@NotNull WebSession session,
@NotNull String nodePath) throws DBWebException;
@WebAction
boolean setNavigatorNodeFilter(
@NotNull WebSession webSession,
@NotNull String nodePath,
@Nullable List<String> include,
@Nullable List<String> exclude) throws DBWebException;
@WebAction
boolean refreshNavigatorNode(
@NotNull WebSession session,
@@ -77,4 +84,5 @@ public interface DBWServiceNavigator extends DBWService {
@NotNull WebSession session,
@NotNull List<String> nodePaths,
@NotNull String folderPath) throws DBWebException;
}
@@ -36,6 +36,7 @@ import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
import org.jkiss.dbeaver.model.struct.rdb.DBSProcedure;
import org.jkiss.dbeaver.registry.DataSourceFolder;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
@@ -133,13 +134,16 @@ public class WebNavigatorNodeInfo {
public boolean isNavigable() {
if (node instanceof DBNDatabaseNode) {
DBNDatabaseNode databaseNode = (DBNDatabaseNode) this.node;
if (!databaseNode.getMeta().isNavigable()) {
return false;
}
return databaseNode.getMeta().isNavigable();
}
return true;
}
@Property
public boolean isFiltered() {
return node.isFiltered();
}
@Property
public boolean isHasChildren() {
return node.hasChildren(true);
@@ -258,6 +262,15 @@ public class WebNavigatorNodeInfo {
return null;
}
@Property
public DBSObjectFilter getFilter() throws DBWebException {
if (!(node instanceof DBNDatabaseFolder)) {
throw new DBWebException("Invalid navigator node type: " + node.getClass().getName());
}
DBSObjectFilter filter = ((DBNDatabaseFolder) node).getNodeFilter(((DBNDatabaseFolder) node).getItemsMeta(), true);
return filter == null || filter.isEmpty() || !filter.isEnabled() ? null : filter;
}
@Override
public String toString() {
return node.getNodeItemPath();
@@ -63,6 +63,12 @@ public class WebServiceBindingNavigator extends WebServiceBindingBase<DBWService
));
model.getMutationType()
.dataFetcher("navSetFolderFilter", env -> getService(env).setNavigatorNodeFilter(
getWebSession(env),
env.getArgument("nodePath"),
env.getArgument("include"),
env.getArgument("exclude")
))
.dataFetcher("navRenameNode", env -> getService(env).renameNode(
getWebSession(env),
env.getArgument("nodePath"),
@@ -17,7 +17,9 @@
package io.cloudbeaver.service.navigator.impl;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebCommandContext;
import io.cloudbeaver.model.WebConnectionInfo;
@@ -43,13 +45,12 @@ import org.jkiss.dbeaver.model.edit.DBEObjectRenamer;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.exec.DBCExecutionContextDefaults;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
import org.jkiss.dbeaver.model.rm.RMResource;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog;
import org.jkiss.dbeaver.model.struct.rdb.DBSSchema;
import org.jkiss.dbeaver.model.websocket.WSConstants;
@@ -207,6 +208,45 @@ public class WebServiceNavigator implements DBWServiceNavigator {
}
}
@Override
public boolean setNavigatorNodeFilter(
@NotNull WebSession webSession,
@NotNull String nodePath,
@Nullable List<String> include,
@Nullable List<String> exclude) throws DBWebException {
try {
DBRProgressMonitor monitor = webSession.getProgressMonitor();
DBNNode node = webSession.getNavigatorModel().getNodeByPath(monitor, nodePath);
if (node == null) {
throw new DBWebException("Navigator node '" + nodePath + "' not found");
}
if (!(node instanceof DBNDatabaseFolder)) {
throw new DBWebException("Invalid navigator node type: " + node.getClass().getName());
}
DBSObjectFilter filter = new DBSObjectFilter();
if (!CommonUtils.isEmpty(include)) {
filter.setInclude(include);
}
if (!CommonUtils.isEmpty(exclude)) {
filter.setExclude(exclude);
}
filter.setEnabled(true);
((DBNDatabaseFolder) node).setNodeFilter(
((DBNDatabaseFolder) node).getItemsMeta(), filter, true);
if (hasNodeEditPermission(webSession, node, ((WebProjectImpl) node.getOwnerProject()).getRmProject())) {
// Save settings
((DBNDatabaseFolder) node).getDataSourceContainer().persistConfiguration();
}
} catch (DBException e) {
if (e instanceof DBWebException) {
throw (DBWebException)e;
}
throw new DBWebException("Error changing navigator node '" + nodePath + "' filters", e);
}
return true;
}
@Override
public boolean refreshNavigatorNode(
@NotNull WebSession session,
@@ -425,12 +465,9 @@ public class WebServiceNavigator implements DBWServiceNavigator {
String projectId = rmNode.getResourceProject().getId();
// Get paths from nodes
String resourcePath = rmNode.getResourceFolder();
RMController rmController = session.getRmController();
var oldRmResourcePath = rmController.getResourcePath(projectId, resourcePath);
node.rename(session.getProgressMonitor(), newName);
var newPath = rmNode.getResourceFolder();
var newRmResourcePath = rmController.getResourcePath(projectId, newPath);
addRmMoveEvent(session, projectId, resourcePath, newPath, oldRmResourcePath, newRmResourcePath);
addRmMoveEvent(session, projectId, resourcePath, newPath);
return node.getName();
}
@@ -438,22 +475,18 @@ public class WebServiceNavigator implements DBWServiceNavigator {
@NotNull WebSession session,
String projectId,
String oldResourcePath,
String newResourcePath,
RMResource[] oldRmResourcePath,
RMResource[] newRmResourcePath
String newResourcePath
) {
WebEventUtils.addRmResourceUpdatedEvent(
projectId,
session,
oldResourcePath,
oldRmResourcePath,
WSConstants.EventAction.DELETE,
WSResourceProperty.NAME);
WebEventUtils.addRmResourceUpdatedEvent(
projectId,
session,
newResourcePath,
newRmResourcePath,
WSConstants.EventAction.CREATE,
WSResourceProperty.NAME);
}
@@ -515,13 +548,11 @@ public class WebServiceNavigator implements DBWServiceNavigator {
DBNResourceManagerResource rmResource = ((DBNResourceManagerResource) node);
String resourceProjectId = rmResource.getResourceProject().getId();
String resourcePath = rmResource.getResourceFolder();
var rmResourcePath = session.getRmController().getResourcePath(resourceProjectId, resourcePath);
session.getRmController().deleteResource(resourceProjectId, resourcePath, true);
WebEventUtils.addRmResourceUpdatedEvent(
resourceProjectId,
session,
resourcePath,
rmResourcePath,
WSConstants.EventAction.DELETE,
WSResourceProperty.NAME);
}
@@ -537,9 +568,8 @@ public class WebServiceNavigator implements DBWServiceNavigator {
}
private void checkProjectEditAccess(DBNNode node, WebSession session) throws DBException {
var project = session.getProjectById(node.getOwnerProject().getId());
if (project == null || !hasNodeEditPermission(session, node, project.getRmProject())
) {
BaseWebProjectImpl project = (BaseWebProjectImpl) node.getOwnerProject();
if (project == null || !hasNodeEditPermission(session, node, project.getRmProject())) {
throw new DBException("Access denied");
}
}
@@ -614,11 +644,8 @@ public class WebServiceNavigator implements DBWServiceNavigator {
newPath = ((DBNResourceManagerResource) folderNode).getResourceFolder() + "/" + newPath;
}
String resourcePath = rmOldNode.getResourceFolder();
RMController rmController = session.getRmController();
var oldRmResourcePath = rmController.getResourcePath(projectId, resourcePath);
rmController.moveResource(projectId, resourcePath, newPath);
var newRmResourcePath = rmController.getResourcePath(projectId, newPath);
addRmMoveEvent(session, projectId, resourcePath, newPath, oldRmResourcePath, newRmResourcePath);
session.getRmController().moveResource(projectId, resourcePath, newPath);
addRmMoveEvent(session, projectId, resourcePath, newPath);
} else {
throw new DBWebException("Navigator node '" + path + "' is not a data source node");
}
@@ -326,11 +326,18 @@ public class WebSessionManager {
/**
* Send session state with remaining alive time to all cached session
*/
public void sendSessionsStates() throws DBException {
public void sendSessionsStates() {
synchronized (sessionMap) {
for (var session : sessionMap.values()) {
session.getUserContext().refreshPermissions();
session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid()));
if (session instanceof WebHeadlessSession) {
continue;
}
try {
session.getUserContext().refreshPermissions();
session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid()));
} catch (Exception e) {
log.error("Failed to refresh session state: " + session.getSessionId(), e);
}
}
}
}
@@ -1,14 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.76.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.78.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Require-Bundle: io.cloudbeaver.server,
org.jkiss.dbeaver.registry
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.service.admin
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Metadata
@@ -2,6 +2,5 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.76-SNAPSHOT</version>
<version>1.0.78-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -114,11 +114,16 @@ input ServerConfigInput {
sessionExpireTime: Int
}
extend type Query {
input AdminUserFilterInput {
userIdMask: String
enabledState: Boolean
}
extend type Query {
#### Users and teams
listUsers(userId: ID): [AdminUserInfo!]!
adminUserInfo(userId: ID!): AdminUserInfo!
listUsers(page: PageInput!, filter: AdminUserFilterInput!): [AdminUserInfo!]!
listTeams(teamId: ID): [AdminTeamInfo!]!
listPermissions: [AdminPermissionInfo!]!
listAuthRoles: [String!]!
@@ -138,6 +143,8 @@ extend type Query {
setUserCredentials(userId: ID!, providerId: ID!, credentials: Object!): Boolean
deleteUserCredentials(userId: ID!, providerId: ID!): Boolean
enableUser(userId: ID!, enabled: Boolean!): Boolean
setUserAuthRole(userId: ID!, authRole: String): Boolean
@@ -0,0 +1,47 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2023 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.admin;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBPPage;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.security.user.SMUserFilter;
import java.util.Map;
public class AdminUserInfoFilter {
@NotNull
private final SMUserFilter filter;
public AdminUserInfoFilter(@NotNull Map<String, Object> params, @NotNull Map<String, Object> pageParams) {
DBPPage page = new DBPPage(
JSONUtils.getInteger(pageParams, "offset", 0),
JSONUtils.getInteger(pageParams, "limit", Integer.MAX_VALUE)
);
Boolean enabledState = params.containsKey("enabledState") ? JSONUtils.getBoolean(params, "enabledState") : null;
this.filter = new SMUserFilter(
JSONUtils.getString(params, "userIdMask"),
enabledState,
page
);
}
public SMUserFilter getFilter() {
return filter;
}
}
@@ -31,7 +31,6 @@ import org.jkiss.dbeaver.model.security.SMDataSourceGrant;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Web service API
@@ -40,7 +39,12 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
List<AdminUserInfo> listUsers(@NotNull WebSession webSession, @Nullable String userName) throws DBWebException;
AdminUserInfo getUserById(@NotNull WebSession webSession, @NotNull String userId) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
List<AdminUserInfo> listUsers(@NotNull WebSession webSession, AdminUserInfoFilter filter)
throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
@@ -91,6 +95,9 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean setUserCredentials(@NotNull WebSession webSession, @NotNull String userID, @NotNull String providerId, @NotNull Map<String, Object> credentials) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteUserCredentials(@NotNull WebSession webSession, @NotNull String userId, @NotNull String providerId) throws DBWebException;
////////////////////////////////////////////////////////////////////
// Connection management
@@ -31,7 +31,8 @@ import org.jkiss.utils.CommonUtils;
/**
* Web service implementation
*/
public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmin> implements DBWServiceBindingServlet<CBApplication> {
public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmin>
implements DBWServiceBindingServlet<CBApplication> {
private static final String SCHEMA_FILE_NAME = "schema/service.admin.graphqls";
@@ -42,8 +43,17 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
@Override
public void bindWiring(DBWBindingContext model) throws DBWebException {
model.getQueryType()
.dataFetcher("listUsers",
env -> getService(env).listUsers(getWebSession(env), env.getArgument("userId")))
.dataFetcher(
"adminUserInfo",
env -> getService(env).getUserById(getWebSession(env), env.getArgument("userId"))
)
.dataFetcher(
"listUsers",
env -> getService(env).listUsers(
getWebSession(env),
new AdminUserInfoFilter(env.getArgument("filter"), env.getArgument("page"))
)
)
.dataFetcher("listTeams",
env -> getService(env).listTeams(getWebSession(env), env.getArgument("teamId")))
.dataFetcher("listPermissions",
@@ -84,6 +94,8 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
env -> getService(env).setSubjectPermissions(getWebSession(env), env.getArgument("subjectId"), env.getArgument("permissions")))
.dataFetcher("setUserCredentials",
env -> getService(env).setUserCredentials(getWebSession(env), env.getArgument("userId"), env.getArgument("providerId"), env.getArgument("credentials")))
.dataFetcher("deleteUserCredentials",
env -> getService(env).deleteUserCredentials(getWebSession(env), env.getArgument("userId"), env.getArgument("providerId")))
.dataFetcher("enableUser",
env -> getService(env).enableUser(getWebSession(env), env.getArgument("userId"), env.getArgument("enabled")))
.dataFetcher("setUserAuthRole",
@@ -22,7 +22,6 @@ import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
@@ -66,16 +65,26 @@ public class WebServiceAdmin implements DBWServiceAdmin {
.flatMap(service -> service.getPermissions().stream())
.collect(Collectors.toMap(WebPermissionDescriptor::getId, Function.identity()));
@NotNull
@Override
public AdminUserInfo getUserById(@NotNull WebSession webSession, @NotNull String userId) throws DBWebException {
try {
SMUser smUser = webSession.getAdminSecurityController().getUserById(userId);
return new AdminUserInfo(webSession, new WebUser(smUser));
} catch (Exception e) {
throw new DBWebException("Error getting user - " + userId, e);
}
}
@NotNull
@Override
public List<AdminUserInfo> listUsers(@NotNull WebSession webSession, String userName) throws DBWebException {
public List<AdminUserInfo> listUsers(@NotNull WebSession webSession, @NotNull AdminUserInfoFilter adminFilter) throws DBWebException {
try {
List<AdminUserInfo> webUsers = new ArrayList<>();
for (SMUser smUser : webSession.getAdminSecurityController().findUsers(userName)) {
webUsers.add(new AdminUserInfo(webSession, new WebUser(smUser)));
List<AdminUserInfo> users = new ArrayList<>();
for (SMUser smUser : webSession.getAdminSecurityController().findUsers(adminFilter.getFilter())) {
users.add(new AdminUserInfo(webSession, new WebUser(smUser)));
}
return webUsers;
return users;
} catch (Exception e) {
throw new DBWebException("Error reading users", e);
}
@@ -318,6 +327,20 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
}
@Override
public boolean deleteUserCredentials(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull String providerId
) throws DBWebException {
try {
webSession.getAdminSecurityController().deleteUserCredentials(userId, providerId);
return true;
} catch (Exception e) {
throw new DBWebException("Error setting user credentials", e);
}
}
@Override
public Boolean enableUser(@NotNull WebSession webSession, @NotNull String userID, @NotNull Boolean enabled) throws DBWebException {
WebUser grantor = webSession.getUser();
@@ -1,14 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.76.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.78.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Require-Bundle: io.cloudbeaver.server
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.service.auth
Export-Package: io.cloudbeaver.service.auth
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Metadata
@@ -2,6 +2,5 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.76-SNAPSHOT</version>
<version>1.0.78-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -58,6 +58,7 @@ type AuthProviderInfo {
defaultProvider: Boolean!
trusted: Boolean!
private: Boolean!
supportProvisioning: Boolean!
# Configurable providers must be configured first. See configurations field.
configurable: Boolean!
@@ -33,15 +33,17 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMStandardMeta;
import org.jkiss.dbeaver.model.security.exception.SMException;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RPSessionHandler implements DBWSessionHandler {
@@ -68,15 +70,23 @@ public class RPSessionHandler implements DBWSessionHandler {
SMAuthProviderExternal<?> authProviderExternal = (SMAuthProviderExternal<?>) authProvider.getInstance();
String userName = request.getHeader(RPAuthProvider.X_USER);
String teams = request.getHeader(RPAuthProvider.X_ROLE);
String firstName = request.getHeader(RPAuthProvider.X_FIRST_NAME);
String lastName = request.getHeader(RPAuthProvider.X_LAST_NAME);
List<String> userTeams = teams == null ? Collections.emptyList() : List.of(teams.split("\\|"));
if (userName != null) {
try {
Map<String, Object> credentials = new HashMap<>();
credentials.put("user", userName);
if (!CommonUtils.isEmpty(firstName)) {
credentials.put(SMStandardMeta.META_FIRST_NAME, firstName);
}
if (!CommonUtils.isEmpty(lastName)) {
credentials.put(SMStandardMeta.META_LAST_NAME, lastName);
}
Map<String, Object> sessionParameters = webSession.getSessionParameters();
sessionParameters.put(SMConstants.SESSION_PARAM_TRUSTED_USER_TEAMS, userTeams);
Map<String, Object> userCredentials = authProviderExternal.authExternalUser(
webSession.getProgressMonitor(), sessionParameters, credentials);
webSession.getProgressMonitor(), null, credentials);
String currentSmSessionId = webSession.getUser() == null ? null : webSession.getUserContext().getSmSessionId();
try {
SMAuthInfo smAuthInfo = securityController.authenticate(
@@ -1,15 +1,14 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.77.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.79.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Require-Bundle: io.cloudbeaver.server,
org.jkiss.dbeaver.data.transfer,
org.jkiss.dbeaver.registry
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.service.data.transfer
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Data Transfer
@@ -2,6 +2,5 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.77-SNAPSHOT</version>
<version>1.0.79-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.80.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.82.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Metadata
@@ -2,6 +2,5 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.80-SNAPSHOT</version>
<version>1.0.82-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,14 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: %Bundle-Vendor
Bundle-Name: %Bundle-Name
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.29.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.31.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Require-Bundle: io.cloudbeaver.server,
io.cloudbeaver.service.admin
Bundle-Localization: OSGI-INF/l10n/bundle
Automatic-Module-Name: io.cloudbeaver.service.rm
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Resource manager
@@ -2,6 +2,5 @@ source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
OSGI-INF/,\
schema/,\
plugin.xml
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.29-SNAPSHOT</version>
<version>1.0.31-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -118,7 +118,6 @@ public class WebServiceRM implements DBWServiceRM {
projectId,
webSession,
resourcePath,
getResourceController(webSession).getResourcePath(projectId, resourcePath),
WSConstants.EventAction.UPDATE,
WSResourceProperty.PROPERTY,
propertyName);
@@ -156,7 +155,6 @@ public class WebServiceRM implements DBWServiceRM {
projectId,
webSession,
resourcePath,
getResourceController(webSession).getResourcePath(projectId, resourcePath),
WSConstants.EventAction.CREATE,
WSResourceProperty.NAME);
return result;
@@ -172,13 +170,11 @@ public class WebServiceRM implements DBWServiceRM {
) throws DBException {
checkIsRmEnabled(webSession);
try {
var rmResourcePath = getResourceController(webSession).getResourcePath(projectId, resourcePath);
getResourceController(webSession).deleteResource(projectId, resourcePath, false);
WebEventUtils.addRmResourceUpdatedEvent(
projectId,
webSession,
resourcePath,
rmResourcePath,
WSConstants.EventAction.DELETE,
WSResourceProperty.NAME);
return true;
@@ -196,22 +192,18 @@ public class WebServiceRM implements DBWServiceRM {
checkIsRmEnabled(webSession);
try {
var resourceController = getResourceController(webSession);
var oldRmResourcePath = resourceController.getResourcePath(projectId, oldResourcePath);
resourceController.moveResource(projectId, oldResourcePath, newResourcePath);;
var newRmResourcePath = resourceController.getResourcePath(projectId, newResourcePath);
resourceController.moveResource(projectId, oldResourcePath, newResourcePath);
WebEventUtils.addRmResourceUpdatedEvent(
projectId,
webSession,
oldResourcePath,
oldRmResourcePath,
WSConstants.EventAction.DELETE,
WSResourceProperty.NAME);
WebEventUtils.addRmResourceUpdatedEvent(
projectId,
webSession,
newResourcePath,
newRmResourcePath,
WSConstants.EventAction.CREATE,
WSResourceProperty.NAME);
return true;
@@ -241,7 +233,6 @@ public class WebServiceRM implements DBWServiceRM {
projectId,
webSession,
resourcePath,
getResourceController(webSession).getResourcePath(projectId, resourcePath),
eventType,
WSResourceProperty.CONTENT);
return content;
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Security Sevice
Bundle-Vendor: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.32.qualifier
Bundle-Release-Date: 20230710
Bundle-Version: 1.0.34.qualifier
Bundle-Release-Date: 20230807
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -1,2 +0,0 @@
Bundle-Vendor = DBeaver Corp
Bundle-Name = Cloudbeaver Web Service - Security
@@ -3,5 +3,4 @@ output.. = target/classes/
bin.includes = .,\
META-INF/,\
db/,\
plugin.xml,\
OSGI-INF/
plugin.xml
@@ -15,7 +15,11 @@
</credentials>
</authProvider>
<authProvider id="reverseProxy" label="Reverse proxy" description="Reverse proxy header based authentication" trusted="true" class="io.cloudbeaver.auth.provider.rp.RPAuthProvider">
<authProvider id="reverseProxy" label="Reverse proxy"
description="Reverse proxy header based authentication"
trusted="true"
class="io.cloudbeaver.auth.provider.rp.RPAuthProvider"
icon="platform:/plugin/org.jkiss.dbeaver.model/icons/tree/key.png">
<credentials>
<propertyGroup label="General">
<property id="user" label="User name" type="string" description="User name" admin="true" user="true" identifying="true"/>
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.32-SNAPSHOT</version>
<version>1.0.34-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -26,6 +26,7 @@ import org.jkiss.dbeaver.model.auth.AuthPropertyEncryption;
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.utils.CommonUtils;
@@ -74,7 +75,12 @@ public class LocalAuthProvider implements SMAuthProvider<LocalAuthSession> {
}
@Override
public LocalAuthSession openSession(@NotNull DBRProgressMonitor monitor, @NotNull SMSession mainSession, @NotNull Map<String, Object> providerConfig, @NotNull Map<String, Object> userCredentials) throws DBException {
public LocalAuthSession openSession(
@NotNull DBRProgressMonitor monitor,
@NotNull SMSession mainSession,
@Nullable SMAuthProviderCustomConfiguration customConfiguration,
@NotNull Map<String, Object> userCredentials
) throws DBException {
String userName = CommonUtils.toString(userCredentials.get(CRED_USER));
if (CommonUtils.isEmpty(userName)) {
throw new DBException("Invalid user name");
@@ -27,9 +27,14 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPObject;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMStandardMeta;
import org.jkiss.utils.CommonUtils;
import java.util.HashMap;
import java.util.Map;
public class RPAuthProvider implements SMAuthProviderExternal<SMSession> {
@@ -38,15 +43,19 @@ public class RPAuthProvider implements SMAuthProviderExternal<SMSession> {
public static final String X_USER = "X-User";
public static final String X_ROLE = "X-Role";
public static final String X_FIRST_NAME = "X-First-name";
public static final String X_LAST_NAME = "X-Last-name";
public static final String AUTH_PROVIDER = "reverseProxy";
@NotNull
@Override
public String validateLocalAuth(@NotNull DBRProgressMonitor monitor,
@NotNull SMController securityController,
@NotNull Map<String, Object> providerConfig,
@NotNull Map<String, Object> userCredentials,
@Nullable String activeUserId) throws DBException {
public String validateLocalAuth(
@NotNull DBRProgressMonitor monitor,
@NotNull SMController securityController,
@NotNull Map<String, Object> providerConfig,
@NotNull Map<String, Object> userCredentials,
@Nullable String activeUserId
) throws DBException {
String userName = (String) userCredentials.get("user");
if (activeUserId == null) {
@@ -58,9 +67,31 @@ public class RPAuthProvider implements SMAuthProviderExternal<SMSession> {
@NotNull
@Override
public DBWUserIdentity getUserIdentity(@NotNull DBRProgressMonitor monitor, @NotNull Map<String, Object> providerConfig, @NotNull Map<String, Object> authParameters) throws DBException {
public DBWUserIdentity getUserIdentity(
@NotNull DBRProgressMonitor monitor,
@Nullable SMAuthProviderCustomConfiguration customConfiguration,
@NotNull Map<String, Object> authParameters
) throws DBException {
String userName = String.valueOf(authParameters.get("user"));
return new DBWUserIdentity(userName, userName);
StringBuilder nameBuilder = new StringBuilder();
Map<String, String> userMeta = new HashMap<>();
String firstName = JSONUtils.getString(authParameters, SMStandardMeta.META_FIRST_NAME);
String lastName = JSONUtils.getString(authParameters, SMStandardMeta.META_LAST_NAME);
if (CommonUtils.isNotEmpty(firstName)) {
nameBuilder.append(firstName);
userMeta.put(SMStandardMeta.META_FIRST_NAME, firstName);
}
if (CommonUtils.isNotEmpty(lastName)) {
nameBuilder.append(lastName);
userMeta.put(SMStandardMeta.META_LAST_NAME, lastName);
}
return new DBWUserIdentity(
userName,
nameBuilder.length() > 0 ? nameBuilder.toString() : userName,
userMeta
);
}
@Nullable
@@ -70,7 +101,12 @@ public class RPAuthProvider implements SMAuthProviderExternal<SMSession> {
}
@Override
public SMSession openSession(@NotNull DBRProgressMonitor monitor, @NotNull SMSession mainSession, @NotNull Map<String, Object> providerConfig, @NotNull Map<String, Object> userCredentials) throws DBException {
public SMSession openSession(
@NotNull DBRProgressMonitor monitor,
@NotNull SMSession mainSession,
@Nullable SMAuthProviderCustomConfiguration customConfiguration,
@NotNull Map<String, Object> userCredentials
) throws DBException {
return new LocalAuthSession(mainSession, (String) userCredentials.get("user"));
}
@@ -37,6 +37,7 @@ import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPPage;
import org.jkiss.dbeaver.model.auth.*;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
@@ -49,6 +50,7 @@ import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
import org.jkiss.dbeaver.model.security.exception.SMException;
import org.jkiss.dbeaver.model.security.exception.SMRefreshTokenExpiredException;
import org.jkiss.dbeaver.model.security.user.*;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.SecurityUtils;
@@ -125,6 +127,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
if (isSubjectExists(userId)) {
throw new DBCException("User or team '" + userId + "' already exists");
}
log.debug("Create user: " + userId);
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
createAuthSubject(dbCon, userId, SUBJECT_USER);
@@ -154,6 +157,17 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
}
@Override
public void importUsers(@NotNull SMUserImportList userImportList) throws DBException {
for (SMUserProvisioning user : userImportList.getUsers()) {
if (isSubjectExists(user.getUserId())) {
log.info("Skip already exist user: " + user.getUserId());
continue;
}
createUser(user.getUserId(), user.getMetaParameters(), true, userImportList.getAuthRole());
}
}
@Override
public void deleteUser(String userId) throws DBCException {
invalidateAllUserTokens(userId);
@@ -205,20 +219,22 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
@NotNull
@Override
public SMTeam[] getUserTeams(String userId) throws DBException {
Map<String, SMTeam> teams = new LinkedHashMap<>();
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT R.* FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R " +
"WHERE UR.USER_ID=? AND UR.TEAM_ID=R.TEAM_ID"))
) {
dbStat.setString(1, userId);
List<SMTeam> teams = new ArrayList<>();
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
teams.add(fetchTeam(dbResult));
var team = fetchTeam(dbResult);
teams.put(team.getTeamId(), team);
}
}
return teams.toArray(new SMTeam[0]);
}
readSubjectsMetas(dbCon, SMSubjectType.team, null, teams);
return teams.values().toArray(new SMTeam[0]);
} catch (SQLException e) {
throw new DBCException("Error while reading user teams", e);
}
@@ -291,19 +307,45 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
return getUserById(getUserIdOrThrow());
}
@Override
public int countUsers(@NotNull SMUserFilter filter) throws DBCException {
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(
"SELECT COUNT(*) FROM {table_prefix}CB_USER" + buildUsersFilter(filter)))) {
setUsersFilterValues(dbStat, filter, 1);
try (ResultSet dbResult = dbStat.executeQuery()) {
if (dbResult.next()) {
return dbResult.getInt(1);
} else {
return 0;
}
}
}
} catch (SQLException e) {
throw new DBCException("Error while counting users", e);
}
}
@NotNull
@Override
public SMUser[] findUsers(String userNameMask) throws DBCException {
return findUsers(new SMUserFilter(userNameMask, null, new DBPPage(0, Integer.MAX_VALUE)));
}
@NotNull
@Override
public SMUser[] findUsers(@NotNull SMUserFilter filter)
throws DBCException {
try (Connection dbCon = database.openConnection()) {
Map<String, SMUser> result = new LinkedHashMap<>();
// Read users
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER" +
(CommonUtils.isEmpty(userNameMask) ? "\nORDER BY USER_ID" : " WHERE USER_ID=?")))
) {
if (!CommonUtils.isEmpty(userNameMask)) {
dbStat.setString(1, userNameMask);
}
database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER"
+ buildUsersFilter(filter) + "\nORDER BY USER_ID LIMIT ? OFFSET ?"))) {
int parameterIndex = setUsersFilterValues(dbStat, filter, 1);
dbStat.setInt(parameterIndex++, filter.getPage().getLimit());
dbStat.setInt(parameterIndex++, filter.getPage().getOffset());
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
String userId = dbResult.getString(1);
@@ -313,14 +355,22 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
}
}
readSubjectsMetas(dbCon, SMSubjectType.user, userNameMask, result);
if (result.isEmpty()) {
return new SMUser[0];
}
readSubjectsMetas(dbCon, SMSubjectType.user, filter.getUserIdMask(), result);
StringBuilder teamsSql = new StringBuilder()
.append("SELECT USER_ID,TEAM_ID FROM {table_prefix}CB_USER_TEAM")
.append("\n")
.append("WHERE USER_ID IN (")
.append(SQLUtils.generateParamList(result.size()))
.append(")");
// Read teams
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID,TEAM_ID FROM {table_prefix}CB_USER_TEAM" +
(CommonUtils.isEmpty(userNameMask) ? "" : " WHERE USER_ID=?")))
) {
if (!CommonUtils.isEmpty(userNameMask)) {
dbStat.setString(1, userNameMask);
try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(teamsSql.toString()))) {
int parameterIndex = 1;
for (String userId : result.keySet()) {
dbStat.setString(parameterIndex++, userId);
}
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
@@ -340,6 +390,33 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
}
private String buildUsersFilter(SMUserFilter filter) {
StringBuilder where = new StringBuilder();
List<String> whereParts = new ArrayList<>();
if (!CommonUtils.isEmpty(filter.getUserIdMask())) {
whereParts.add("USER_ID LIKE ?");
}
if (filter.getEnabledState() != null) {
whereParts.add("IS_ACTIVE=?");
}
if (whereParts.size() > 0) {
where.append(whereParts.stream().collect(Collectors.joining(" AND ", " WHERE ", "")));
}
return where.toString();
}
private int setUsersFilterValues(PreparedStatement dbStat, SMUserFilter filter, int parameterIndex)
throws SQLException {
if (!CommonUtils.isEmpty(filter.getUserIdMask())) {
dbStat.setString(parameterIndex++, "%" + filter.getUserIdMask() + "%");
}
if (filter.getEnabledState() != null) {
dbStat.setString(parameterIndex++, filter.getEnabledState() ? CHAR_BOOL_TRUE : CHAR_BOOL_FALSE);
}
return parameterIndex;
}
private void cleanupSubjectMeta(Connection dbCon, String subjectId) throws SQLException {
// Delete old metas
try (PreparedStatement dbStat = dbCon.prepareStatement(
@@ -367,17 +444,18 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
}
private void readSubjectsMetas(Connection dbCon, SMSubjectType subjectType, String nameMask, Map<String, ? extends SMSubject> result) throws SQLException {
private void readSubjectsMetas(Connection dbCon, SMSubjectType subjectType, String userIdMask,
Map<String, ? extends SMSubject> result) throws SQLException {
// Read metas
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT m.SUBJECT_ID,m.META_ID,m.META_VALUE FROM {table_prefix}CB_AUTH_SUBJECT s, " +
"{table_prefix}CB_SUBJECT_META m\n" +
"WHERE s.SUBJECT_TYPE=? AND s.SUBJECT_ID=m.SUBJECT_ID" +
(CommonUtils.isEmpty(nameMask) ? "" : " AND s.SUBJECT_ID=?")))
(CommonUtils.isEmpty(userIdMask) ? "" : " AND s.SUBJECT_ID LIKE ?")))
) {
dbStat.setString(1, subjectType.getCode());
if (!CommonUtils.isEmpty(nameMask)) {
dbStat.setString(2, nameMask);
if (!CommonUtils.isEmpty(userIdMask)) {
dbStat.setString(2, "%" + userIdMask + "%");
}
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
@@ -604,6 +682,20 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
}
@Override
public void deleteUserCredentials(@NotNull String userId, @NotNull String authProviderId) throws DBException {
try (Connection dbCon = database.openConnection()) {
JDBCUtils.executeStatement(
dbCon,
database.normalizeTableNames("DELETE FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?"),
userId,
authProviderId
);
} catch (SQLException e) {
throw new DBCException("Error deleting user credentials", e);
}
}
@Nullable
private String findUserByCredentials(WebAuthProviderDescriptor authProvider, Map<String, Object> authParameters) throws DBCException {
Map<String, Object> identCredentials = new LinkedHashMap<>();
@@ -1093,7 +1185,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
var smTokens = generateNewSessionToken(smSessionId, null, null, dbCon);
var permissions = getAnonymousUserPermissions();
txn.commit();
return SMAuthInfo.success(
return SMAuthInfo.successMainSession(
UUID.randomUUID().toString(),
smTokens.getSmAccessToken(),
smTokens.getSmRefreshToken(),
@@ -1125,12 +1217,22 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
var authProgressMonitor = new LoggingProgressMonitor(log);
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
boolean isMainSession = previousSmSessionId == null;
Map<String, Object> securedUserIdentifyingCredentials = userCredentials;
WebAuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId);
var authProviderInstance = authProviderDescriptor.getInstance();
SMAuthProviderCustomConfiguration providerConfig = authProviderConfigurationId == null
? null
: application.getAuthConfiguration().getAuthProviderConfiguration(authProviderConfigurationId);
if (SMAuthProviderExternal.class.isAssignableFrom(authProviderInstance.getClass())) {
var authProviderExternal = (SMAuthProviderExternal<?>) authProviderInstance;
securedUserIdentifyingCredentials = authProviderExternal.authExternalUser(authProgressMonitor, Map.of(), userCredentials);
securedUserIdentifyingCredentials = authProviderExternal.authExternalUser(
authProgressMonitor,
providerConfig,
userCredentials
);
}
var filteredUserCreds = filterSecuredUserData(
@@ -1146,7 +1248,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
appSessionId,
previousSmSessionId,
sessionType,
sessionParameters
sessionParameters,
isMainSession
);
if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) {
@@ -1162,7 +1265,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
return finishAuthentication(
SMAuthInfo.inProgress(
authAttemptId,
null, Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials)
null,
Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials)
),
true,
false
@@ -1196,7 +1300,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
String appSessionId,
String prevSessionId,
SMSessionType sessionType,
Map<String, Object> sessionParameters
Map<String, Object> sessionParameters,
boolean isMainSession
) throws DBException {
String authAttemptId = UUID.randomUUID().toString();
try (Connection dbCon = database.openConnection()) {
@@ -1399,7 +1504,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
SMTokens smTokens = findTokenBySmSession(smSessionId);
SMAuthPermissions authPermissions = getTokenPermissions(smTokens.getSmAccessToken());
String authRole = readTokenAuthRole(smTokens.getSmAccessToken());
var successAuthStatus = SMAuthInfo.success(
var successAuthStatus = SMAuthInfo.successMainSession(
authId,
smTokens.getSmAccessToken(),
smTokens.getSmRefreshToken(),
@@ -1427,7 +1532,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
for (SMAuthInfo authData : allLatestAuthData) {
mergedData.putAll(authData.getAuthData());
}
return SMAuthInfo.success(
return SMAuthInfo.successMainSession(
"restore_session_attempt_" + UUID.randomUUID(),
latestActiveSmTokens.getAccessToken(),
latestActiveSmTokens.getRefreshToken(),
@@ -1507,6 +1612,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
return currentUserCreds;
}
@NotNull
private SMTokens findTokenBySmSession(String smSessionId) throws DBException {
try (Connection dbCon = database.openConnection();
PreparedStatement dbStat = dbCon.prepareStatement(
@@ -1607,12 +1713,13 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
SMTokens smTokens = null;
SMAuthPermissions permissions = null;
String activeUserId = null;
if (!isMainAuthSession) {
var accessToken = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId()).getSmAccessToken();
//this is an additional authorization and we should to return the original permissions and userId
smTokens = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId());
permissions = getTokenPermissions(smTokens.getSmAccessToken());
permissions = getTokenPermissions(accessToken);
activeUserId = permissions.getUserId();
}
String activeUserId = permissions == null ? null : permissions.getUserId();
Map<SMAuthConfigurationReference, Object> storedUserData = new LinkedHashMap<>();
SMTeam[] allTeams = null;
@@ -1678,7 +1785,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
String tokenAuthRole = updateUserAuthRoleIfNeeded(activeUserId, detectedAuthRole);
if (smTokens == null && permissions == null) {
if (isMainAuthSession) {
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
String smSessionId;
@@ -1708,15 +1815,24 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
}
var authStatus = forceExpireAuthAfterSuccess ? SMAuthStatus.EXPIRED : SMAuthStatus.SUCCESS;
updateAuthStatus(authId, authStatus, storedUserData, null, permissions.getSessionId());
return SMAuthInfo.success(
authId,
smTokens.getSmAccessToken(),
//refresh token must be sent only once
isMainAuthSession ? smTokens.getSmRefreshToken() : null,
permissions,
authInfo.getAuthData(),
tokenAuthRole
);
if (isMainAuthSession) {
return SMAuthInfo.successMainSession(
authId,
smTokens.getSmAccessToken(),
//refresh token must be sent only from main session
smTokens.getSmRefreshToken(),
permissions,
authInfo.getAuthData(),
tokenAuthRole
);
} else {
return SMAuthInfo.successChildSession(
authId,
permissions,
authInfo.getAuthData()
);
}
}
private void autoUpdateUserTeams(
@@ -1967,6 +2083,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
return getTokenPermissions(activeUserCredentials.getSmAccessToken());
}
@NotNull
private SMAuthPermissions getTokenPermissions(@NotNull String token) throws DBException {
String userId;
String sessionId;
@@ -2074,6 +2191,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
if (CommonUtils.isEmpty(subjectIds) || CommonUtils.isEmpty(objectIds)) {
return;
}
Set<String> filteredSubjects = getFilteredSubjects(subjectIds);
// validatePermissions(objectType.getObjectType(), permissions);
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
@@ -2096,6 +2214,10 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
dbStat.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
dbStat.setString(4, grantor);
for (String subjectId : subjectIds) {
if (!filteredSubjects.contains(subjectId)) {
log.error("Subject '" + subjectId + "' is not found in database");
continue;
}
dbStat.setString(5, subjectId);
for (String permission : permissions) {
dbStat.setString(6, permission);
@@ -2427,4 +2549,29 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
throw new DBCException("Error deleting auth attempt info", e);
}
}
public Set<String> getFilteredSubjects(Set<String> allSubjects) {
try (Connection dbCon = database.openConnection()) {
Set<String> result = new HashSet<>();
var sqlBuilder = new StringBuilder("SELECT SUBJECT_ID FROM {table_prefix}CB_AUTH_SUBJECT U ")
.append("WHERE SUBJECT_ID IN (")
.append(SQLUtils.generateParamList(allSubjects.size()))
.append(")");
try (var dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder.toString()))) {
int parameterIndex = 1;
for (String subjectId : allSubjects) {
dbStat.setString(parameterIndex++, subjectId);
}
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
result.add(dbResult.getString(1));
}
}
};
return result;
} catch (SQLException e) {
log.error("Error getting all subject ids from database", e);
return Set.of();
}
}
}
@@ -60,6 +60,7 @@ import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* Database management
@@ -275,7 +276,7 @@ public class CBDatabase {
}
@Nullable
CBDatabaseInitialData getInitialData() {
CBDatabaseInitialData getInitialData() throws DBException {
String initialDataPath = databaseConfiguration.getInitialDataConfiguration();
if (CommonUtils.isEmpty(initialDataPath)) {
return null;
@@ -287,8 +288,7 @@ public class CBDatabase {
Gson gson = new GsonBuilder().setLenient().create();
return gson.fromJson(reader, CBDatabaseInitialData.class);
} catch (Exception e) {
log.error("Error loading initial data configuration", e);
return null;
throw new DBException("Error loading initial data configuration", e);
}
}
@@ -412,15 +412,26 @@ public class CBDatabase {
String adminName = initialData.getAdminName();
String adminPassword = initialData.getAdminPassword();
if (!CommonUtils.isEmpty(initialData.getTeams())) {
List<SMTeam> initialTeams = initialData.getTeams();
String defaultTeam = application.getAppConfiguration().getDefaultUserTeam();
if (CommonUtils.isNotEmpty(defaultTeam)) {
Set<String> initialTeamNames = initialTeams == null
? Set.of()
: initialTeams.stream().map(SMTeam::getTeamId).collect(Collectors.toSet());
if (!initialTeamNames.contains(defaultTeam)) {
throw new DBException("Initial teams configuration doesn't contain default team " + defaultTeam);
}
}
if (!CommonUtils.isEmpty(initialTeams)) {
// Create teams
for (SMTeam team : initialData.getTeams()) {
for (SMTeam team : initialTeams) {
adminSecurityController.createTeam(team.getTeamId(), team.getName(), team.getDescription(), adminName);
if (adminName != null && !application.isMultiNode()) {
adminSecurityController.setSubjectPermissions(team.getTeamId(),
adminSecurityController.setSubjectPermissions(
team.getTeamId(),
new ArrayList<>(team.getPermissions()),
adminName);
adminName
);
}
}
}
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>io.trino</groupId>
<artifactId>trino-jdbc</artifactId>
<version>351</version>
<version>422</version>
</dependency>
</dependencies>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.100.qualifier"
version="1.0.102.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.100-SNAPSHOT</version>
<version>1.0.102-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="23.1.2.qualifier"
version="23.1.4.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>23.1.2-SNAPSHOT</version>
<version>23.1.4-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>

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