Compare commits

...
Author SHA1 Message Date
Ruslan 6f5ac673cf dbeaver/pro#6976 poc log masking 2025-09-25 19:06:45 +02:00
Sychev AndreyandDaria Marutkina 25247321b8 dbeaver/pro#5471 [CB] Add shortcuts to tooltips data editor toolbar (#3750)
* dbeaver/pro#5471 fix: "Delete" data viewer action tooltip

* dbeaver/pro#5471 refactor: "merge" save and save as script actions

We used to have two buttons with different shortcuts for saving SQL as a script(first save) and saving already created script (updating). On UI they had the same icon and were more or less identical for the user. We decided to use the same shortcut and labels for both buttons (mod + S).

* dbeaver/pro#5471 feat: adds Find and Comment shortcuts to the shortcuts list

* dbeaver/pro#5471 refactor: use common l10n keys for save/undo/redo

* dbeaver/pro#5471 refactor: use key bindings API for Delete action in TableView

* dbeaver/pro#5471 refactor: use key bindings API for add, duplicate and revert actions

* dbeaver/pro#5471 fix: extra import

* dbeaver/pro#5471 refactor: don't use a symbol for delete shortcut

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-25 18:18:17 +08:00
Sychev Andrey 72975abef0 dbeaver/pro#6142 fix: can't expand nodes using filter (#3773)
If folder node had not been loaded before the filter is used, we would get into situation that filtering manually expanding this node without children and we would loose the ability to load them by expanding by click(our main flow is to load on expand).

This fix removes the auto expanding on filtering. Now it works similarly to the main navigation tree with filters
2025-09-25 18:17:23 +08:00
Alexey Potsetsuev 1fadf5009d dbeaver/pro#6968 fix: eslint (#3777) 2025-09-25 17:48:58 +08:00
Alexander Skoblikovandkseniaguzeeva 23090e286a dbeaver/pro#5999 do not link shared credentials with users (#3748)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-09-24 16:55:51 +02:00
Alexey Potsetsuevandmr-anton-t 1cacf6a5cc dbeaver/pro#6980 fix: elements ordering (#3770)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-24 21:57:11 +08:00
d0e86745f1 dbeaver/pro#6520 fix: header cell layout (#3759)
* dbeaver/pro#6520 fix: header cell layout

* dbeaver/pro#6520 refactor: table cell always shows 2 rows

* dbeaver/pro#6520 fix: use two rows grid only if a table has any description

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-24 16:30:59 +08:00
Alexander SkoblikovandMashaKorax 97d179a103 dbeaver/pro#6229 execute sql via cli (#3721)
* dbeaver/pro#6229 execute sql via cli

* dbeaver/pro#6229 review fix

* dbeaver/pro#6229 update run scripts

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-09-23 18:48:26 +02:00
sergeyteleshev 37b8716aee dbeaver/pro#6935 replaces sass-embedded with sass (#3771) 2025-09-24 00:34:16 +08:00
Alexey Potsetsuevandsergeyteleshev d1c2d6547f dbeaver/pro#6968 feat: add storybook (#3767)
* dbeaver/pro#6968 feat: add storybook

* chore: remove duplicate

* fix: ladle

* chore: remove storybook onboarding

* adds task to run storybook

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-09-23 21:02:07 +08:00
4f53ece164 dbeaver/pro#6327 refactor: sql editor & vqb (#3742)
* dbeaver/pro#6327 refactor: sql editor & vqb

* fix: state sync

* fix: segment resolution

* chore: add comment

* refactor: remove old code, add todo about problem

* fix: yarn.lock

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-23 18:45:45 +08:00
Alexey Potsetsuevandmr-anton-t 0fe432f45c dbeaver/pro#6783 refactor: plugins bundles (#3761)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-23 18:37:24 +08:00
Sychev AndreyandDaria Marutkina 58b8eafc7b dbeaver/pro#6892 fix: letter spacing in combobox item (#3747)
Make the same as in combobox input

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-23 18:26:35 +08:00
alexandDaria Marutkina cdb4c7e1fc dbeaver/pro#6924 pass selectedValue to provider (#3752)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-23 00:03:25 +08:00
alexandDaria Marutkina 3a2d4f807d dbeaver/pro#5752 add boolean component to metadata editor (#3754)
* dbeaver/pro#5752 add boolean component to metadata editor

* dbeaver/pro#5752 fix linter

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-22 23:34:43 +08:00
39ea1e2b7f dbeaver/pro#4742 add tree context menu provider (#3745)
* dbeaver/pro#4742 add tree context menu provider

* dbeaver/pro#4742 add useTreeMenu hook

* dbeaver/pro#4742 revert hook renaming

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-22 19:43:27 +08:00
Daria Marutkina da7d4eef19 dbeaver/pro#6916 Update README file (#3764) 2025-09-22 11:50:45 +02:00
alexandDaria Marutkina d23ab441ff dbeaver/pro#6305 fix input label hover blinking (#3753)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-22 17:07:14 +08:00
serge-rider 220c59ed01 dbeaver/pro#6952 Use ALL-DEFAULT modules mode 2025-09-21 11:40:36 +02:00
serge-rider eb0f3db330 dbeaver/pro#6759 CLI model 2025-09-20 07:36:00 +02:00
Serge Rider 3babafaecd dbeaver/pro#6759 dbvr app stub and config 2025-09-19 14:49:59 +02:00
serge-rider 4765629537 dbeaver/pro#6934 XML parser annotations 2025-09-19 08:25:22 +02:00
Serge RiderandMatvey16 534526e5e4 dbeaver/pro#6775 AI functions API (#3712)
Co-authored-by: Matvey16 <82543000+Matvey16@users.noreply.github.com>
2025-09-17 12:25:48 +02:00
dbeaver-devops 2d6ebd91f1 dbeaver/pro#6916 25.2.2 version bump (#3749) 2025-09-16 19:05:20 +02:00
Ainur e6e92f0bb3 dbeaver/pro#5614 api for expert settings props (#3727) 2025-09-16 13:51:38 +00:00
5a394e648d [CB-6198] Migrate secrets ui (public) from backend to frontend part (#3701)
* dbeaver/pro#5170 adds refactoring boilerplate

* dbeaver/pro#6198 remove redundant code about secret configuration

* moves secrets into provider properties

* renames optionsExtendedForm to ProviderPropertiesExtendedForm

* build fix

* dbeaver/pro#5170 set external params fix

* moves providerProperties container to ConnectionFormService

* cleanup

* adds empty arrays to connection secret reset state

* truncates select items and select itself

* Revert "truncates select items and select itself"

This reverts commit 56c943b5a0.

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-09-16 15:05:28 +02:00
sergeyteleshevandmr-anton-t e466462269 [CB-4362] Add trim to all inputs in forms (#3687)
* dbeaver/pro#4696 adds zod schema parsing for form parts

* adds validation error to context

* adds schema parsing for UserFormInfoPart & ConnectionFormOptionsPart

* spreads RecordKeySchema across zod validations

* fixes form editing for connection info part

* Revert "spreads RecordKeySchema across zod validations"

This reverts commit 0e6719b32f.

* removes connection form errors during saving when empty fields

* reverts connection form options part parsing

* removes trimming from teams & authRole

* build fix

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-15 21:47:55 +08:00
sergeyteleshevandmr-anton-t 1c57e670c0 [CB] get back switch title bold styles (#3738)
* dbeaver/pro#6878 gets back switch bold title

* incapsulates field styles only

* moves all composes out of the layer

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-15 15:16:36 +08:00
alexandDaria Marutkina 3808af50c7 dbeaver/pro#5014 use debounce instead of throttle (#3734)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-15 00:14:56 +02:00
Alexander Skoblikov 390452d8a6 dbeaver/pro#6880 more correct origin parsing (#3741) 2025-09-12 15:27:00 +00:00
Alexey Potsetsuevandmr-anton-t b733f395d3 dbeaver/pro#6847 fix: elements order (#3729)
* dbeaver/pro#6847 fix: elements order

* fix: linting

* fix: actions order

* fix: actions order

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-12 23:21:54 +08:00
sergeyteleshev e31ae9a439 dbeaver/pro#6881 updates deps (#3743) 2025-09-12 19:57:54 +08:00
sergeyteleshevandDaria Marutkina 07f60c0034 [CB] Bump NPM dependencies (#3723)
* dbeaver/pro#5765 updates deps

* updates @wroud/vite-plugin-tsc

* updates @vitejs/plugin-react

* removes sass-embedded

* resolves sass to sass-embedded

* build fix

* linter fix

* handles empty json table edge case

* removes redundant translations

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-12 17:39:48 +08:00
sergeyteleshevandEvgenia ba32c12b9c [CB-5023] Add vizualization for focus on switchers (#3677)
* dbeaver/pro#4862 adds focus on switches

* fixes border radius

* less border-radius

* applies hack with borders and position to save border radius and offset

* removes variable

* fix: applies focus only for tab

* removes focus on ESC button

* removes onkeydown

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-11 17:32:33 +08:00
Ainur ca3ce9b79d dbeaver/pro#6786 load engine properties from object (#3732) 2025-09-11 08:38:04 +00:00
96d6dba83a dbeaver/pro#6644 update licence status after users decrease (#3711)
* dbeaver/pro#6644 update licence status after users decrease

* dbeaver/pro#6644 update licence status after user decrease

* dbeaver/pro#6644 update license status after users decrease

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-09-11 08:20:14 +02:00
Ruslan MusaevandEvgenia 6ce5ef235c dbeaver/pro#6259 fix query logging for import (#3733)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-11 08:13:20 +02:00
a1404e3495 dbeaver/pro#6224 Add soring completion alphabetically for web (#3697)
* dbeaver/pro#6224 Add soring completion alphabetically for web

* dbeaver/pro#6224 Add soring completion alphabetically for web

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Elizabeth <28875055+E1izabeth@users.noreply.github.com>
2025-09-10 23:44:11 +03:00
sergeyteleshev d4e2f2365a [CB] application freezes during loading on heavy resultsets (ORIGINAL FIX) (#3717)
* dbeaver/pro#6731 enables virtualization always

* removes default virtualization prop

* [CB] application freezes during loading on heavy resultsets (OPTIONAL FIXES) (#3715)

* dbeaver/pro#6731 adds some optimizations

* cleanup

* pr fixes

* cleanup

* removes optimizations

* linter fixes
2025-09-10 21:06:54 +08:00
ab813e21ab dbeaver/pro#5129 cb 6095 sql editor buttons shortcut labels are hardcoded (#3696)
* dbeaver/pro#5129 feat: use action item bindings API to get binding label

removes hardcoded shortcuts
adds formatting for shortcuts, uses OS-specific shortcuts

* dbeaver/pro#5129 feat: add example of manual using getBindingLabel function

* dbeaver/pro#5129 feat: use menu api for sql editor actions

* dbeaver/pro#5129 fix: lint async

* dbeaver/pro#5129 fix: remove wrong action icons

move private methods to the end of files

* dbeaver/pro#5129 refactor: use context via useDataContextLink hook

* dbeaver/pro#5129 refactor: DRY ACTIONS

* dbeaver/pro#5129 fix: DRY ACTIONS

* dbeaver/pro#5129 fix: action titles

* dbeaver/pro#5129 fix: DI after devel merge

* dbeaver/pro#5129 bonus: fix text content in dropdowns

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
2025-09-10 21:04:03 +08:00
AinurandDaria Marutkina c5190b2567 dbeaver/pro#6249 fix events for changing extensions (#3667)
* dbeaver/pro#6249 fix events for changing extensions

* dbeaver/pro#6249 send connection updated info event on rm side

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-10 14:33:34 +02:00
alexandEvgenia e896789cb0 dbeaver/pro#6155 do not set whereFilter on constraint change (#3702)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-10 18:39:02 +08:00
Serge Riderandmr-anton-t 60827d769a dbeaver/pro#6791 LDAP icon fix (#3714)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-09 12:36:29 +02:00
Alexey Potsetsuevandmr-anton-t 65876e9b4e dbeaver/pro#6744 fix: elements order (#3706)
* dbeaver/pro#6744 fix: elements order

* fix: fixate position of elements

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-04 02:40:01 +08:00
Alexander SkoblikovandMashaKorax 048206266d dbeaver/pro#5878 cli custom exit code (#3705)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-09-03 10:49:12 +02:00
Alexey Potsetsuev f41b9d24fe dbeaver/pro#6662 chore: cleanup (#3713) 2025-09-02 18:41:49 +08:00
Daria MarutkinaandAnastasiya 03dabc0e1b dbeaver/pro#6715 Update README file (#3709)
* dbeaver/pro#6715 Update README file

* Update README.md

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>

* Update README.md

* Update README.md

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>

---------

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>
2025-09-01 13:03:44 +02:00
Daria Marutkina 56946742c2 dbeaver/pro#6715 Update README file (#3708) 2025-09-01 11:21:23 +02:00
serge-rider e1e4ae0ba6 dbeaver/pro#6773 Move ldap icon to cloudbeaver 2025-09-01 07:29:53 +02:00
Alexey Potsetsuevandmr-anton-t 80f258b561 dbeaver/pro#6744 refactor: migration to @wroud/di (#3704)
* dbeaver/pro#6744 refactor: migration to `@wroud/di`

* tests: enable settings tests

* chore: update syntax

* fix: dynamic theme detection

* fix: skip not stable test

* fix: skip whole test

* fix: make theme optional

* fix: page reload

* tests: skip unstable test

* fix: remove optional

* fix: load system theme

* fix: prevent the system theme from always loading despite not being selected

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-08-29 18:22:11 +08:00
Alexander SkoblikovandDaria Marutkina ac48e13039 dbeaver/pro#6703 use local host address by default (#3695)
* dbeaver/pro#6703 use local host address by default

* dbeaver/pro#6703 rename method

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-27 12:20:18 +02:00
3877e914c4 dbeaver/pro#6462 error instead of redirect for gql requests (#3690)
* dbeaver/pro#6462 error instead of redirect for gql requests

* feat: force redirect on supportedHosts change

* fix: replace redirect with warning message

* fix: prevent active host removal

* fix: error message and ignore when on IP address

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-08-26 15:01:25 +02:00
sergeyteleshev 564ceb7d82 dbeaver/pro#6708 adds @dbeaver/react-tests to packages to execute tests locally (#3698) 2025-08-25 20:42:44 +08:00
serge-rider 622b5133c2 dbeaver/pro#6715 25.2.1 version bump 2025-08-22 18:55:11 +02:00
1436 changed files with 11141 additions and 18898 deletions
+3 -22
View File
@@ -49,21 +49,14 @@ jobs:
node-version: "lts/*"
cache: "yarn"
cache-dependency-path: |
cloudbeaver/common-*/yarn.lock
cloudbeaver/webapp/yarn.lock
- name: restore cloudbeaver-common typescript cache
uses: actions/cache@v4
with:
path: "cloudbeaver/common-*/**/*/lib"
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
restore-keys: |
${{ runner.os }}-common-dist-
- name: restore typescript cache
uses: actions/cache@v4
with:
path: "cloudbeaver/webapp/**/packages/*/dist"
path: |
"cloudbeaver/webapp/common-*/**/*/lib"
"cloudbeaver/webapp/**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
@@ -71,23 +64,11 @@ jobs:
- if: inputs.clean-cache
run: yarn clear
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-typescript
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-react
- run: yarn install --immutable
- run: yarn bundle
working-directory: ./cloudbeaver/webapp/packages/product-default
- run: yarn test
working-directory: ./cloudbeaver/common-typescript
- run: yarn test
working-directory: ./cloudbeaver/common-react
- run: yarn test
# - name: Archive build artifacts
+2 -2
View File
@@ -30,10 +30,10 @@ jobs:
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/common-typescript
# working-directory: ./cloudbeaver/webapp/common-typescript
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/common-react
# working-directory: ./cloudbeaver/webapp/common-react
- run: |
yarn install --immutable
+1 -1
View File
@@ -48,7 +48,7 @@
"-Declipse.pde.launch=true",
"-XstartOnFirstThread",
"-Dfile.encoding=UTF-8",
"--add-modules=ALL-SYSTEM",
"--add-modules=ALL-DEFAULT",
"--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",
+12 -3
View File
@@ -26,7 +26,7 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/common-typescript"
"cwd": "${workspaceFolder}/webapp/common-typescript"
}
},
{
@@ -36,7 +36,7 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/common-react"
"cwd": "${workspaceFolder}/webapp/common-react"
}
},
{
@@ -133,9 +133,18 @@
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
},
"args": ["docs"]
},
{
"label": "Run Storybook",
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/webapp/packages/storybook"
},
"args": ["storybook"]
}
],
"inputs": [
@@ -2,6 +2,6 @@
<?pde version="3.8"?>
<target name="org.jkiss.cloudbeaver.tp">
<locations>
<location path="../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
<location path="../../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
</locations>
</target>
</target>
+27 -52
View File
@@ -25,67 +25,42 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.1.5 2025-08-18
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. Use the Allowed Server URLs field in the Server configuration within the Administration section to set it up;
- Added the Force HTTPS mode setting in Server Configuration to enforce secure communication between the server and clients, recommended for production environments. This ensures all connections use HTTPS, improving security when the proxy is correctly configured. This setting is disabled by default;
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: If a user’s internet connection changes (e.g., switching networks or using mobile data), they will be automatically - Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
### 25.2.1 2025-09-22
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search.
- "Show all databases" setting was added on the connection page for MySQL and MariaDB.
- Different bug fixes and enhancements have been made.
### 25.1.4 2025-08-04
- Added an ability to review the script before execution when users edit tables without primary keys in the Data Editor;
- DuckDB: driver has been updated to version 1.3.2;
- Databend: driver has been updated to version 0.3.9.
### 25.1.3 2025-07-21
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Editor;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
### 25.1.2 2025-07-07
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added a button “Clear” to the Output panel;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- PostgresSQL: fixed misplaced comment for table DDL generation.
### 25.1.1 2025-06-23
### 25.2.0 2025-09-01
### Changes since 25.1.0:
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
- Databend database support has been added.
### 25.1.0 2025-06-02
### Changes since 25.0.0:
- Important:
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
- Administration:
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
- LDAP authorization:
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
- SQL Editor:
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
- Auto-completion has been improved for aliases and camelCase entities.
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
- A “Clear” button was added to the output panel.
- Data Editor:
- Improved table rendering to scale column width depending on the content;
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
- Added an ability to review the script before execution when users edit tables without primary keys.
- General:
- Added the ability to configure the default database or schema in the connection configuration;
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
- Databases and drivers:
- Clickhouse: driver has been updated to version 0.8.5;
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
- LibSQL: added the ability to connect using token authentication;
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
- Databend database support has been added (thanks to @hantmac);
- DuckDB: driver has been updated to version 1.3;
- MySQL: Improved performance when retrieving foreign keys metadata;
- PostgreSQL: fixed misplaced comment for table DDL generation.
## Contribution
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
The most significant contribution to our code for the major release 25.1.0 was made by:
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
The most significant contribution to our code for the major release 25.2.0 was made by:
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
-9
View File
@@ -1,9 +0,0 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
-7
View File
@@ -1,7 +0,0 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
-5
View File
@@ -1,5 +0,0 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
-7
View File
@@ -1,7 +0,0 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}
@@ -1,3 +0,0 @@
export * from './isDefined.js';
export * from './isNotNullDefined.js';
export * from './mutex.js';
File diff suppressed because it is too large Load Diff
+2 -10
View File
@@ -5,12 +5,8 @@ echo "Build static content"
mkdir -p ./cloudbeaver/web
cd ../../cloudbeaver/common-typescript
cd ../../cloudbeaver/webapp
yarn install --immutable
cd ../common-react
yarn install --immutable
cd ../webapp
yarn install --immutable
cd ./packages/product-default
yarn run bundle
@@ -19,11 +15,7 @@ if [[ "$?" -ne 0 ]] ; then
echo 'Application build failed'; exit $rc
fi
cd ../../../common-typescript
yarn test
cd ../common-react
yarn test
cd ../webapp
cd ../../
yarn test
if [[ "$?" -ne 0 ]] ; then
+1 -1
View File
@@ -17,7 +17,7 @@ If Not Defined JAVA_OPTS (
java %JAVA_OPTS% ^
-Dfile.encoding=UTF-8 ^
--add-modules=ALL-SYSTEM ^
--add-modules=ALL-DEFAULT ^
--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 ^
+2 -2
View File
@@ -11,7 +11,7 @@ echo "Starting Cloudbeaver Server"
exec java ${JAVA_OPTS} \
-Dfile.encoding=UTF-8 \
--add-modules=ALL-SYSTEM \
--add-modules=ALL-DEFAULT \
--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 \
@@ -36,6 +36,6 @@ exec java ${JAVA_OPTS} \
-jar ${launcherJar} \
-product io.cloudbeaver.product.ce.product \
-web-config conf/cloudbeaver.conf \
${CLI_OPTS} \
"$@" \
-nl en \
-registryMultiLanguage
+4 -3
View File
@@ -1,8 +1,9 @@
#!/bin/bash
set -Eeo pipefail
CLI_OPTS="$@"
if [[ $# -eq 0 ]] ;
then
CLI_OPTS='-help'
set -- "$@" -help
fi
source run-server.sh
set -- "$@" -cli-mode
source run-server.sh "$@"
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.84.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 1.0.86.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
@@ -10,4 +10,10 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.deploymentType.n
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbUrl.name=Security manager database URL
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbDriverName.name=Security manager database driver name
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.name=Security manager database product name
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
@@ -0,0 +1,2 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Garder actif (en secondes)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Pas de déconnexion automatique
@@ -0,0 +1,5 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
@@ -0,0 +1,4 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
@@ -0,0 +1,3 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.84-SNAPSHOT</version>
<version>1.0.86-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -35,6 +35,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
import org.jkiss.dbeaver.registry.DataSourceConfigurationManager;
import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
@@ -377,7 +378,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
}
@Override
public boolean loadDataSources(
public DataSourceParseResults loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@NotNull DataSourceConfigurationManager manager,
@Nullable Collection<String> dataSourceIds, boolean refresh,
@@ -29,13 +29,16 @@ import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WebSessionProjectImpl extends WebProjectImpl {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
@@ -194,40 +197,31 @@ public class WebSessionProjectImpl extends WebProjectImpl {
/**
* updates data sources based on event in web session
*
* @param dataSourceIds list of updated connections
* @param eventId id of event
* @param event data source updated event
*/
public synchronized boolean updateProjectDataSources(@NotNull List<String> dataSourceIds, @NotNull String eventId) {
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
var sendDataSourceUpdatedEvent = false;
DBPDataSourceRegistry registry = getDataSourceRegistry();
// save old connections
var oldDataSources = dataSourceIds.stream()
.map(registry::getDataSource)
.filter(Objects::nonNull)
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
registry::createDataSource)
);
if (WSDataSourceEvent.CREATED.equals(eventId) || WSDataSourceEvent.UPDATED.equals(eventId)) {
registry.refreshConfig(dataSourceIds);
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
registry.refreshConfig(event.getDataSourceIds());
}
for (String dsId : dataSourceIds) {
for (String dsId : event.getDataSourceIds()) {
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
if (ds == null) {
continue;
}
switch (eventId) {
switch (event.getId()) {
case WSDataSourceEvent.CREATED -> {
addConnection(ds);
sendDataSourceUpdatedEvent = true;
}
case WSDataSourceEvent.UPDATED -> {
boolean connectionUpdated = !ds.equalSettings(oldDataSources.get(dsId));
if (connectionUpdated) {
sendDataSourceUpdatedEvent = true;
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
}
// if settings were changed we need to send event
if (event.getProperty() != WSDataSourceProperty.INTERNAL) {
sendDataSourceUpdatedEvent = true;
}
}
case WSDataSourceEvent.DELETED -> {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
@@ -56,7 +56,9 @@ public class WebConnectionConfig {
private boolean saveCredentials;
private boolean sharedCredentials;
private Map<String, Object> mainPropertyValues;
private Map<String, Object> expertSettingsValues;
private Map<String, Object> providerProperties;
private Map<String, Object> externalParameters;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;
@@ -71,7 +73,6 @@ public class WebConnectionConfig {
if (!CommonUtils.isEmpty(params)) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
readOnly = JSONUtils.getBoolean(params, "readOnly");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
@@ -79,9 +80,6 @@ public class WebConnectionConfig {
databaseName = JSONUtils.getString(params, "databaseName");
url = JSONUtils.getString(params, "url");
keepAliveInterval = JSONUtils.getInteger(params, "keepAliveInterval", -1);
defaultAutoCommit = JSONUtils.getBoolean(params, "autocommit", true);
name = JSONUtils.getString(params, "name");
description = JSONUtils.getString(params, "description");
folder = JSONUtils.getString(params, "folder");
@@ -98,11 +96,22 @@ public class WebConnectionConfig {
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
defaultCatalogName = JSONUtils.getString(params, "defaultCatalogName");
defaultSchemaName = JSONUtils.getString(params, "defaultSchemaName");
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
keepAliveInterval = JSONUtils.getInteger(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
readOnly = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
defaultAutoCommit = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
defaultCatalogName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
defaultSchemaName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA);
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
externalParameters = JSONUtils.getObjectOrNull(params, "externalParameters");
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
@@ -219,6 +228,11 @@ public class WebConnectionConfig {
return mainPropertyValues;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
return expertSettingsValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
@@ -248,4 +262,8 @@ public class WebConnectionConfig {
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public Map<String, Object> getExternalParameters() {
return externalParameters;
}
}
@@ -417,6 +417,17 @@ public class WebConnectionInfo {
return mainProperties;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
Map<String, Object> expertSettings = new LinkedHashMap<>();
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA, getDefaultSchemaName());
return expertSettings;
}
@Property
public Map<String, String> getProviderProperties() {
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
@@ -0,0 +1,87 @@
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBPObject;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
/**
* Web expert settings properties. Class for returning filtered expert settings properties in web interface.
*/
public class WebExpertSettingsProperties implements DBPObject {
public static final String PROP_READ_ONLY = "readOnly";
public static final String PROP_AUTO_COMMIT = "autocommit";
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
private final DBPDriver driver;
public WebExpertSettingsProperties(@NotNull DBPDriver driver) {
this.driver = driver;
}
@Property(order = 1, id = PROP_KEEP_ALIVE_INTERVAL, visibleIf = KeepAliveIntervalFieldValidator.class)
public int getKeepAliveInterval() {
return 0;
}
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
public boolean isAutoCommit() {
return true;
}
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
public boolean isReadOnly() {
return false;
}
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
public String getDefaultCatalog() {
return null;
}
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
public String getDefaultSchema() {
return null;
}
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
}
}
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
}
}
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
}
}
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
}
}
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
}
}
}
@@ -198,6 +198,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@NotNull
@Override
public Object start(IApplicationContext context) {
initializeApplicationServices();
@@ -210,8 +211,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
startServer();
} catch (Exception e) {
log.error(e.getMessage(), e);
return EXIT_ERROR_UNSPECIFIED;
}
return null;
return EXIT_OK;
}
protected abstract void startServer() throws DBException;
@@ -20,7 +20,7 @@ import org.apache.commons.cli.CommandLine;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
import org.jkiss.dbeaver.model.cli.CmdProcessResult;
import org.jkiss.dbeaver.model.cli.CLIProcessResult;
import java.io.IOException;
@@ -31,7 +31,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
@NotNull
@Override
public CmdProcessResult handleCommandLine(@NotNull String[] args) {
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
try {
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
@@ -40,7 +40,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
false
);
} catch (Exception e) {
return new CmdProcessResult(CmdProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
}
}
}
@@ -0,0 +1,14 @@
package io.cloudbeaver.model.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a field or method as sensitive, so its value won't be logged.
*/
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
}
@@ -149,27 +149,37 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
return updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds) != null;
}
@Nullable
protected DataSourceParseResults updateProjectDataSourcesConfig(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(projectId, project.getMetadataFolder(false),
return doFileWriteOperation(
projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes(
StandardCharsets.UTF_8));
DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer();
var configChanged = ((DataSourcePersistentRegistry) registry).loadDataSources(
final DataSourceParseResults parseResults = ((DataSourcePersistentRegistry) registry).loadDataSources(
List.of(storage),
manager,
dataSourceIds,
true,
false
dataSourceIds == null
);
registry.checkForErrors();
log.debug("Save data sources configuration in project '" + projectId + "'");
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return configChanged;
return parseResults;
}
);
}
@@ -217,6 +227,7 @@ public abstract class BaseLocalResourceController implements RMController {
var parent = result.getParent();
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -244,6 +255,7 @@ public abstract class BaseLocalResourceController implements RMController {
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
}
}
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -265,6 +277,7 @@ public abstract class BaseLocalResourceController implements RMController {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
registry.moveFolder(oldPath, newPath);
registry.checkForErrors();
((DataSourcePersistentRegistry) registry).saveDataSources();
return null;
}
);
@@ -28,18 +28,26 @@ 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.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentials;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.websocket.event.MessageType;
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -334,6 +342,239 @@ public class LocalResourceController extends BaseLocalResourceController {
return project;
}
@Override
public void createProjectDataSources(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
super.createProjectDataSources(projectId, configuration, dataSourceIds);
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
dataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
}
@Override
public boolean updateProjectDataSources(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
DBPDataSourceRegistry registry = getWebProject(projectId, false).getDataSourceRegistry();
Map<String, DataSourceDescriptor> oldDataSources = registry.getDataSources().stream()
.filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId()))
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
registry::createDataSource
)
);
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
return parseResults != null;
}
@Override
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
super.deleteProjectDataSources(projectId, dataSourceIds);
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
Arrays.asList(dataSourceIds),
WSDataSourceProperty.CONFIGURATION
)
);
}
}
@Override
public void createProjectDataSourceFolder(@NotNull String projectId, @NotNull String folderPath) throws DBException {
super.createProjectDataSourceFolder(projectId, folderPath);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, folderPath))
)
);
}
}
@Override
public void moveProjectDataSourceFolder(
@NotNull String projectId,
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
super.moveProjectDataSourceFolder(projectId, oldPath, newPath);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, oldPath))
)
);
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, newPath))
)
);
}
}
@Override
public void deleteProjectDataSourceFolders(@NotNull String projectId, @NotNull String[] folderPaths, boolean dropContents)
throws DBException {
super.deleteProjectDataSourceFolders(projectId, folderPaths, dropContents);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
Arrays.stream(folderPaths).map(
p -> createNodePathFromFolderPath(projectId, p)
).collect(Collectors.toList())
)
);
}
}
private String createNodePathFromFolderPath(String projectId, String folderPath) {
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
}
private void sendDataSourcesConfigUpdatedEvent(
@NotNull DBPDataSourceRegistry registry,
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
@Nullable DataSourceParseResults parseResults
) {
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
return;
}
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
List<String> updatedNameDataSourceIds = new ArrayList<>();
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
for (Map.Entry<String, DataSourceDescriptor> entry : oldDataSources.entrySet()) {
String dsId = entry.getKey();
DataSourceDescriptor oldDs = entry.getValue();
DataSourceDescriptor newDs = (DataSourceDescriptor) registry.getDataSource(dsId);
if (newDs == null) {
continue;
}
if (!oldDs.equalConfiguration(newDs)) {
updatedConfigurationDataSourceIds.add(dsId);
} else if (!oldDs.isLooselyEqualTo(newDs)) {
updatedNameDataSourceIds.add(dsId);
} else if (!oldDs.equalInternalConfiguration(newDs)) {
updatedInternalConfigurationDataSourceIds.add(dsId);
}
}
if (!updatedConfigurationDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedConfigurationDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!updatedNameDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.NAME
)
);
}
if (!updatedInternalConfigurationDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedInternalConfigurationDataSourceIds,
WSDataSourceProperty.INTERNAL
)
);
}
if (!parseResults.addedDataSources.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!parseResults.removedDataSources.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!parseResults.addedFolders.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
parseResults.addedFolders.stream().map(
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
).toList()
)
);
}
if (!parseResults.removedFolders.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
parseResults.removedFolders.stream().map(
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
).toList()
)
);
}
}
@NotNull
@Override
public RMResource[] listResources(
@@ -18,9 +18,10 @@ package io.cloudbeaver.model.session;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;
import org.jkiss.utils.HttpConstants;
public class WebHttpRequestInfo {
public static final String USER_AGENT = "User-Agent";
public static final String USER_AGENT = HttpConstants.HEADER_USER_AGENT;
@Nullable
private final String id;
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 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.
@@ -118,6 +118,8 @@ public class WebSessionAuthProcessor {
// We may need to associate new credentials with active user
if (linkWithActiveUser) {
securityController.setCurrentUserCredentials(authProviderDescriptor.getId(), authAttrs);
//link only first(main) credentials
alreadyLoggedIn = true;
}
}
@@ -62,12 +62,12 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
}
@Override
public String getDefaultString(String name) {
public String getDefaultString(@NotNull String name) {
return parentStore.getDefaultString(name);
}
@Override
public boolean isDefault(String name) {
public boolean isDefault(@NotNull String name) {
return !userPreferences.containsKey(name) && parentStore.isDefault(name);
}
@@ -77,7 +77,7 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
}
@Override
public void setToDefault(String name) {
public void setToDefault(@NotNull String name) {
throw new RuntimeException("Not implemented");
}
@@ -98,4 +98,5 @@ public class CBConstants {
public static final String BIND_SESSION_ENABLE = "enable";
public static final String BIND_SESSION_DISABLE = "disable";
public static final String DEFAULT_CLOUD_PROJECT_NAME = "GlobalConfiguration";
}
@@ -1,24 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
public interface HttpConstants {
String CONTENT_TYPE = "Content-Type";
String TYPE_JSON = "application/json";
String HEADER_AUTHORIZATION = "Authorization";
}
@@ -18,6 +18,7 @@ package io.cloudbeaver.server;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.impl.preferences.AbstractPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
@@ -34,37 +35,37 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public boolean contains(String name) {
public boolean contains(@NotNull String name) {
return productConf().containsKey(name) || parentStore.contains(name);
}
@Override
public boolean getBoolean(String name) {
public boolean getBoolean(@NotNull String name) {
return toBoolean(getString(name));
}
@Override
public double getDouble(String name) {
public double getDouble(@NotNull String name) {
return toDouble(getString(name));
}
@Override
public float getFloat(String name) {
public float getFloat(@NotNull String name) {
return toFloat(getString(name));
}
@Override
public int getInt(String name) {
public int getInt(@NotNull String name) {
return toInt(getString(name));
}
@Override
public long getLong(String name) {
public long getLong(@NotNull String name) {
return toLong(getString(name));
}
@Override
public String getString(String name) {
public String getString(@NotNull String name) {
Object value = productConf().get(name);
if (value == null) {
return parentStore.getString(name);
@@ -73,38 +74,38 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public boolean getDefaultBoolean(String name) {
public boolean getDefaultBoolean(@NotNull String name) {
return getBoolean(name);
}
@Override
public double getDefaultDouble(String name) {
public double getDefaultDouble(@NotNull String name) {
return getDouble(name);
}
@Override
public float getDefaultFloat(String name) {
public float getDefaultFloat(@NotNull String name) {
return getFloat(name);
}
@Override
public int getDefaultInt(String name) {
public int getDefaultInt(@NotNull String name) {
return getInt(name);
}
@Override
public long getDefaultLong(String name) {
public long getDefaultLong(@NotNull String name) {
return getLong(name);
}
@Override
public String getDefaultString(String name) {
public String getDefaultString(@NotNull String name) {
// TODO: split product.conf and runtime.product.conf
return getString(name);
}
@Override
public boolean isDefault(String name) {
public boolean isDefault(@NotNull String name) {
return true;
}
@@ -114,68 +115,68 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public void setDefault(String name, double value) {
public void setDefault(@NotNull String name, double value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, float value) {
public void setDefault(@NotNull String name, float value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, int value) {
public void setDefault(@NotNull String name, int value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, long value) {
public void setDefault(@NotNull String name, long value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, String defaultObject) {
public void setDefault(@NotNull String name, @Nullable String defaultObject) {
// do not store global default properties in product.conf
this.parentStore.setDefault(name, defaultObject);
}
@Override
public void setDefault(String name, boolean value) {
public void setDefault(@NotNull String name, boolean value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setToDefault(String name) {
public void setToDefault(@NotNull String name) {
parentStore.setToDefault(name);
}
@Override
public void setValue(String name, double value) {
public void setValue(@NotNull String name, double value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, float value) {
public void setValue(@NotNull String name, float value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, int value) {
public void setValue(@NotNull String name, int value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, long value) {
public void setValue(@NotNull String name, long value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, String value) {
public void setValue(@NotNull String name, @Nullable String value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, boolean value) {
public void setValue(@NotNull String name, boolean value) {
parentStore.setValue(name, value);
}
@@ -292,7 +292,7 @@ public class ServletAppUtils {
}
@NotNull
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
String origin = request.getHeader(HEADER_ORIGIN);
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
@@ -353,7 +353,22 @@ public class ServletAppUtils {
if (CommonUtils.isEmpty(rootUri)) {
return uri;
}
String[] split = uri.split(rootUri);
return split[0];
rootUri = "/" + rootUri;
if (!uri.endsWith(rootUri)) {
rootUri = rootUri + "/";
}
return substringBeforeLast(uri, rootUri);
}
public static String substringBeforeLast(
@NotNull String string,
@NotNull String separator
) {
if (CommonUtils.isNotEmpty(string) && CommonUtils.isNotEmpty(separator)) {
int pos = string.lastIndexOf(separator);
return pos == -1 ? string : string.substring(0, pos);
} else {
return string;
}
}
}
@@ -21,7 +21,6 @@ import io.cloudbeaver.model.WebConnectionFolderInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSourceFolder;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.utils.CommonUtils;
public class WebConnectionFolderUtils {
@@ -45,9 +44,4 @@ public class WebConnectionFolderUtils {
throw new DBWebException("Folder name '" + folderName + "' contains illegal characters: /");
}
}
public static DBPDataSourceFolder createFolder(WebConnectionFolderInfo parentFolder, String newName, DBPDataSourceRegistry registry) throws DBWebException {
DBPDataSourceFolder folder = registry.addFolder(parentFolder == null ? null : parentFolder.getDataSourceFolder(), newName);
return folder;
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 25.2.0.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.2.0-SNAPSHOT</version>
<version>25.2.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.129.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 1.0.131.qualifier
Bundle-Release-Date: 20251006
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.129-SNAPSHOT</version>
<version>1.0.131-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.2.0.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.2.0-SNAPSHOT</version>
<version>25.2.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -103,7 +103,6 @@ public abstract class CBApplication<T extends CBServerConfig>
// Persistence
protected SMAdminController securityController;
private boolean configurationMode = false;
private String localHostAddress;
protected String containerId;
private final List<InetAddress> localInetAddresses = new ArrayList<>();
@@ -226,15 +225,6 @@ public abstract class CBApplication<T extends CBServerConfig>
eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
Location instanceLoc = Platform.getInstanceLocation();
try {
@@ -515,9 +505,10 @@ public abstract class CBApplication<T extends CBServerConfig>
return "";
}
@Nullable
@Override
public String getDefaultProjectName() {
return "GlobalConfiguration";
return CBConstants.DEFAULT_CLOUD_PROJECT_NAME;
}
public boolean isDevelMode() {
@@ -529,7 +520,7 @@ public abstract class CBApplication<T extends CBServerConfig>
}
public String getLocalHostAddress() {
return localHostAddress;
return getServerConfigurationController().getLocalHostAddress();
}
public List<InetAddress> getLocalInetAddresses() {
@@ -44,8 +44,6 @@ import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -66,6 +64,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
protected final Path homeDirectory;
private final Map<String, String> externalProperties = new LinkedHashMap<>();
private final Map<String, Object> originalConfigurationProperties = new LinkedHashMap<>();
private String localHostAddress;
protected CBServerConfigurationController(@NotNull T serverConfiguration, @NotNull Path homeDirectory) {
super(homeDirectory);
@@ -80,6 +79,15 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
@Override
public void loadServerConfiguration(Path configPath) throws DBException {
log.debug("Using configuration [" + configPath + "]");
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
if (!Files.exists(configPath)) {
log.error("Configuration file " + configPath + " doesn't exist. Use defaults.");
@@ -162,12 +170,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = CBConstants.HOST_LOCALHOST;
}
hostName = getLocalHostAddress();
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
}
@@ -669,4 +672,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
public void validateFinalServerConfiguration() throws DBException {
}
public String getLocalHostAddress() {
return localHostAddress;
}
}
@@ -41,30 +41,5 @@ public class WSUserEventHandler<EVENT extends WSAbstractEvent> implements WSEven
case WSUserDisabledEvent e -> sessionManager.closeUserSession(e);
default -> { }
}
switch (event.getId()) {
case WSUserCloseSessionsEvent.ID:
if (event instanceof WSUserCloseSessionsEvent closeSessionsEvent) {
if (closeSessionsEvent.getSessionIds().isEmpty()) {
sessionManager.closeAllSessions(closeSessionsEvent.getSessionId());
} else {
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
}
}
break;
case WSUserDeletedEvent.ID:
if (event instanceof WSUserDeletedEvent userDeletedEvent) {
sessionManager.closeUserSession(userDeletedEvent);
}
break;
case WSUserDisabledEvent.ID:
if (event instanceof WSUserDisabledEvent userDisabledEvent) {
sessionManager.closeUserSession(userDisabledEvent);
}
break;
default:
break;
}
}
}
@@ -120,12 +120,13 @@ public class CBJettyServer {
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
servletContextHandler.addServlet(
new ServletHolder(
"graphql",
endpoint
),
serverConfiguration.getServicesURI() + "gql/*"
gqlServletPath
);
servletContextHandler.addEventListener(new CBServerContextListener(application));
@@ -157,7 +158,11 @@ public class CBJettyServer {
}
}
}
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(application, excludedFilterPaths));
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(
application,
excludedFilterPaths,
Set.of(gqlServletPath)
));
servletContextHandler.addFilter(hostsFilter, "/*", null);
@@ -39,23 +39,35 @@ public class RequestHostFilter implements Filter {
@NotNull
private final CBApplication<?> application;
private final Set<String> excludedPaths = new HashSet<>();
private final Set<String> errorPaths = new HashSet<>();
public RequestHostFilter(@NotNull CBApplication<?> application, @NotNull Set<String> excludedPaths) {
public RequestHostFilter(
@NotNull CBApplication<?> application,
@NotNull Set<String> excludedPaths,
@NotNull Set<String> errorPaths
) {
this.application = application;
this.excludedPaths.addAll(
excludedPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
this.errorPaths.addAll(
errorPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean requestAllowed = true;
if (request instanceof HttpServletRequest httpRequest) {
CBServerConfig serverConfig = application.getServerConfiguration();
URI originUri;
try {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(httpRequest);
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
originUri = URI.create(origin);
} catch (Exception e) {
log.error("Failed to get origin from request", e);
@@ -64,7 +76,8 @@ public class RequestHostFilter implements Filter {
}
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
String servletPath = httpRequest.getServletPath();
if (!isIpAddress) {
requestAllowed = isIpAddress;
if (!requestAllowed) {
if (CommonUtils.isNotEmpty(servletPath)) {
for (String excludedPath : excludedPaths) {
if (servletPath.contains(excludedPath)) {
@@ -73,17 +86,21 @@ public class RequestHostFilter implements Filter {
}
}
}
validateHosts(serverConfig, httpRequest, response, originUri);
requestAllowed = validateHosts(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
if (requestAllowed) {
requestAllowed = validateSchema(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
validateSchema(serverConfig, httpRequest, response, originUri);
}
chain.doFilter(request, response);
if (requestAllowed) {
chain.doFilter(request, response);
}
}
private void validateSchema(
private boolean validateSchema(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull ServletResponse response,
@NotNull HttpServletResponse response,
@NotNull URI originUri
) {
boolean httpsExpected = serverConfig.isForceHttps();
@@ -100,22 +117,24 @@ public class RequestHostFilter implements Filter {
redirectUrlBuilder.append("?")
.append(httpRequest.getQueryString());
}
((HttpServletResponse) response).sendRedirect(redirectUrlBuilder.toString());
response.sendRedirect(redirectUrlBuilder.toString());
return false;
}
} catch (Exception e) {
log.error("Failed to redirect to HTTPS", e);
}
return true;
}
private void validateHosts(
private boolean validateHosts(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull ServletResponse response,
@NotNull HttpServletResponse response,
URI originUri
) throws IOException {
List<String> availableHosts = serverConfig.getSupportedHosts();
if (CommonUtils.isEmpty(availableHosts)) {
return;
return true;
}
try {
var requestHostBuilder = new StringBuilder(originUri.getHost());
@@ -124,13 +143,25 @@ public class RequestHostFilter implements Filter {
}
String requestHost = requestHostBuilder.toString();
if (!availableHosts.contains(requestHost)) {
for (String errorPath : errorPaths) {
if (httpRequest.getServletPath().contains(errorPath)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(
"Request host '" + requestHost + "' is not allowed. Available hosts: " + availableHosts
);
return false;
}
}
log.warn("Request host '" + requestHost + "' is not allowed. Redirect to default: " + availableHosts);
redirectToDefaultHost((HttpServletResponse) response, httpRequest, availableHosts);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
} catch (Throwable e) {
log.error(e.getMessage(), e);
redirectToDefaultHost((HttpServletResponse) response, httpRequest, availableHosts);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
return true;
}
private void redirectToDefaultHost(
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 25.2.0.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
@@ -1,2 +1,2 @@
log.api.graphql.debug = Log GraphQL requests to console
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
@@ -1,2 +1,2 @@
log.api.graphql.debug =Логировать GraphQL-запросы в консоль
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.2.0-SNAPSHOT</version>
<version>25.2.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -351,6 +351,9 @@ type DriverInfo {
"""
providerProperties: [ObjectPropertyInfo!]!
"Expert driver settings properties. Returns properties (like keep-alive interval) that are not often used and can be hidden in UI"
expertSettingsProperties: [ObjectPropertyInfo!]! @since(version: "25.2.1")
"False for drivers which do not support authentication."
anonymousAccess: Boolean
"Default auth model that is used for this driver (see authModels)"
@@ -486,6 +489,9 @@ type ConnectionInfo {
"Main connection properties. Contains host, port, database, server name fields"
mainPropertyValues: Object @since(version: "24.1.2")
"Expert connection settings. Contains expert settings properties (like keep-alive interval or auto-commit) that are not often used"
expertSettingsValues: Object @since(version: "25.2.1")
"Connection keep-alive interval in seconds"
keepAliveInterval: Int!
"Defines if the connection is in auto-commit mode"
@@ -672,6 +678,9 @@ input ConnectionConfig {
"Host, port, serverName, databaseName are also stored in mainPropertyValues for custom pages"
mainPropertyValues: Object @since(version: "24.1.2")
"Keep-alive, auto-commit, read-only and other expert settings are stored in expertSettingsValues"
expertSettingsValues: Object @since(version: "25.2.1")
"Sets connection URL jdbc:{driver}://{host}[:{port}]/[{database}]"
url: String
@@ -679,13 +688,13 @@ input ConnectionConfig {
properties: Object
"Set keep-alive interval"
keepAliveInterval: Int
keepAliveInterval: Int @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets auto-commit connection state"
autocommit: Boolean
autocommit: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets read-only connection state"
readOnly: Boolean
readOnly: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
# User credentials
@@ -721,9 +730,9 @@ input ConnectionConfig {
"Configuration type (MANUAL, URL)"
configurationType: DriverConfigurationType
"Sets catalog name for the connection"
defaultCatalogName: String @since(version: "25.0.5")
defaultCatalogName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets schema name for the connection"
defaultSchemaName: String @since(version: "25.0.5")
defaultSchemaName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
}
####################################################
@@ -41,7 +41,6 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPObject;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -318,9 +317,11 @@ public class WebServiceUtils extends WebCommonUtils {
return container.getName() + " [" + container.getId() + "]";
}
public static void updateConfigAndRefreshDatabases(WebSession session, String projectId) throws DBWebException {
public static void refreshDatabases(WebSession session, String projectId) throws DBWebException {
DBNProject projectNode = session.getNavigatorModelOrThrow().getRoot().getProjectNode(session.getProjectById(projectId));
DBNModel.updateConfigAndRefreshDatabases(projectNode.getDatabases());
if (projectNode != null) {
projectNode.getDatabases().refreshChildren();
}
}
public static boolean isGlobalProject(DBPProject project) {
@@ -363,7 +364,7 @@ public class WebServiceUtils extends WebCommonUtils {
@NotNull
public static WebPropertyInfo[] getObjectFilteredProperties(
@NotNull WebSession session,
@NotNull DBPObject object,
@NotNull Object object,
@Nullable WebPropertyFilter filter
) {
PropertyCollector propertyCollector = new PropertyCollector(object, true);
@@ -18,10 +18,7 @@ package io.cloudbeaver.model;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.utils.ConfigurationUtils;
import io.cloudbeaver.server.WebAppUtils;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
@@ -39,8 +36,6 @@ import org.jkiss.utils.CommonUtils;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Web driver configuration
@@ -257,29 +252,18 @@ public class WebDatabaseDriverInfo {
@Property
public WebPropertyInfo[] getProviderProperties() {
WebPropertyInfo[] additionalWebProperty = Optional.of(WebAppUtils.getWebApplication())
.filter(app -> app.getAppConfiguration().isSecretManagerEnabled())
.map(app -> app.getConnectionController().getExternalInfo(webSession))
.orElse(new WebPropertyInfo[0]);
WebPropertyInfo[] providerProperties = Arrays.stream(driver.getProviderPropertyDescriptors())
return Arrays.stream(driver.getProviderPropertyDescriptors())
.map(p -> new WebPropertyInfo(webSession, p, null))
.toArray(WebPropertyInfo[]::new);
}
return Stream.concat(
Arrays.stream(additionalWebProperty),
Arrays.stream(providerProperties)
).toArray(WebPropertyInfo[]::new);
public WebPropertyInfo[] getExpertSettingsProperties() {
return WebServiceUtils.getObjectFilteredProperties(webSession, new WebExpertSettingsProperties(driver), null);
}
@Property
public boolean isEnabled() {
WebAppConfiguration config = WebAppUtils.getWebApplication().getAppConfiguration();
return ConfigurationUtils.isDriverEnabled(
driver,
config.getEnabledDrivers(),
config.getDisabledDrivers()
);
return WebServiceUtils.isDriverEnabled(driver);
}
@Property
@@ -39,10 +39,7 @@ public class WSDataSourceUpdatedEventHandlerImpl extends WSAbstractProjectEventH
log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId());
return;
}
sendEvent = project.updateProjectDataSources(
event.getDataSourceIds(),
event.getId()
);
sendEvent = project.updateProjectDataSources(event);
}
if (sendEvent) {
activeUserSession.addSessionEvent(event);
@@ -32,7 +32,6 @@ import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.apilog.ApiCallInterceptor;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.HttpConstants;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
@@ -46,6 +45,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import org.jkiss.utils.IOUtils;
import java.io.IOException;
@@ -146,7 +146,7 @@ public class GraphQLEndpoint extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String contentType = request.getContentType();
if (CommonUtils.isEmpty(contentType) || !contentType.startsWith(HttpConstants.TYPE_JSON)) {
if (CommonUtils.isEmpty(contentType) || !contentType.startsWith(HttpConstants.CONTENT_TYPE_JSON)) {
String error = "Bad request," + (CommonUtils.isEmpty(contentType)
? " content type is missing"
: " incorrect content type:" + contentType);
@@ -246,14 +246,14 @@ public class GraphQLEndpoint extends HttpServlet {
if (operationName != null) {
contextBuilder.operationName(operationName);
}
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
// String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
if (operationName != null) {
log.debug("API > " + operationName + loggerMessage);
} else if (DEBUG) {
log.debug("API > " + query + loggerMessage);
}
// String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
// if (operationName != null) {
// log.debug("API > " + operationName + loggerMessage);
// } else if (DEBUG) {
// log.debug("API > " + query + loggerMessage);
// }
LocalDateTime startTime = LocalDateTime.now();
ExecutionInput executionInput = contextBuilder.build();
ExecutionResult executionResult = null;
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.server.graphql;
import io.cloudbeaver.model.log.Sensitive;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
@@ -23,7 +24,9 @@ import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;
import org.jkiss.utils.CommonUtils;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Set;
public class GraphQLLoggerUtil {
@@ -63,43 +66,63 @@ public class GraphQLLoggerUtil {
.findWebSession(request);
}
public static String buildLoggerMessage(String sessionId, String userId, Map<String, Object> variables) {
public static String buildLoggerMessage(String sessionId, String userId, Method method, Object[] args) {
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId).append("]");
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
&& variables != null
// if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
if (true
) {
loggerMessage.append(" [variables] ");
String parsedVariables = parseVarialbes(variables);
String parsedVariables = maskArgsToString(method, args);
if (CommonUtils.isNotEmpty(parsedVariables)) {
loggerMessage.append(parseVarialbes(variables));
loggerMessage.append(parsedVariables);
}
}
return loggerMessage.toString();
}
private static String parseVarialbes(Map<String, Object> map) {
StringBuilder result = new StringBuilder();
public static String maskArgsToString(Method method, Object[] args) {
StringBuilder sb = new StringBuilder();
Parameter[] params = method.getParameters();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
for (int i = 0; i < params.length; i++) {
//fixme can't get real parameter names without -parameters compiler option
String name = params[i].getName();
Object value = (args != null && i < args.length) ? args[i] : null;
boolean isProhibited = PROHIBITED_VARIABLES.stream()
.anyMatch(prohibitedKey -> key.toLowerCase().contains(prohibitedKey.toLowerCase()));
sb.append(name).append(": ");
if (isProhibited) {
result.append(key).append(": ").append("******** ");
continue;
}
if (value instanceof Map) {
result.append(parseVarialbes((Map<String, Object>) value));
if (params[i].isAnnotationPresent(Sensitive.class)) {
sb.append("**** ");
} else if (value != null && !isSimple(value.getClass())) {
sb.append("{ ");
for (Field field : value.getClass().getDeclaredFields()) {
field.setAccessible(true);
sb.append(field.getName()).append(": ");
try {
if (field.isAnnotationPresent(Sensitive.class)) {
sb.append("**** ");
} else {
sb.append(field.get(value)).append(" ");
}
} catch (IllegalAccessException e) {
sb.append("<err> ");
}
}
sb.append("} ");
} else {
result.append(key).append(": ").append(value).append(" ");
sb.append(value).append(" ");
}
}
return result.toString().trim();
return sb.toString().trim();
}
private static boolean isSimple(Class<?> cls) {
return cls.isPrimitive()
|| Number.class.isAssignableFrom(cls)
|| CharSequence.class.isAssignableFrom(cls)
|| Boolean.class.equals(cls)
|| Enum.class.isAssignableFrom(cls);
}
}
@@ -18,7 +18,6 @@ package io.cloudbeaver.server.websockets;
import io.cloudbeaver.model.session.WebHeadlessSession;
import io.cloudbeaver.model.session.WebHttpRequestInfo;
import io.cloudbeaver.server.HttpConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
@@ -33,6 +32,7 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import org.jkiss.utils.WSClientUtils;
import java.util.List;
@@ -21,11 +21,9 @@ import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
@@ -98,5 +96,4 @@ public interface ConnectionController {
void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException;
WebPropertyInfo[] getExternalInfo(WebSession webSession);
}
@@ -20,11 +20,9 @@ import io.cloudbeaver.*;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import io.cloudbeaver.utils.WebEventUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -38,8 +36,6 @@ import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
@@ -100,13 +96,6 @@ public class ConnectionControllerCE implements ConnectionController {
WebConnectionInfo connectionInfo = project.addConnection(newDataSource);
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(
newDataSource));
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
connectionInfo.getId(),
WSConstants.EventAction.CREATE,
WSDataSourceProperty.CONFIGURATION
);
log.info(String.format(
"New connection was created: [info=%s, user=%s]",
WebServiceUtils.getConnectionContainerInfo(newDataSource),
@@ -190,7 +179,6 @@ public class ConnectionControllerCE implements ConnectionController {
DBPDataSourceRegistry sessionRegistry
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId());
boolean sendEvent = !((DataSourceDescriptor) dataSource).equalSettings(getOldDataSource(dataSource));
if (!isSharedCredentials(dataSource)) {
// secret controller is responsible for notification, password changes applied after checks
WebServiceUtils.saveAuthProperties(
@@ -201,24 +189,12 @@ public class ConnectionControllerCE implements ConnectionController {
config.isSharedCredentials()
);
}
WSDataSourceProperty property = getDatasourceEventProperty(getOldDataSource(dataSource), dataSource);
try {
sessionRegistry.updateDataSource(dataSource);
sessionRegistry.checkForErrors();
} catch (DBException e) {
throw new DBWebException("Failed to update connection", e);
}
if (sendEvent) {
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
connectionInfo.getId(),
WSConstants.EventAction.UPDATE,
property
);
}
log.info(String.format(
"Connection updated: [info=%s, userId=%s]",
WebServiceUtils.getConnectionContainerInfo(dataSource),
@@ -238,13 +214,6 @@ public class ConnectionControllerCE implements ConnectionController {
webSession.addInfoMessage("Delete connection - " +
WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()));
closeAndDeleteConnection(webSession, projectId, connectionId, true);
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
connectionId,
WSConstants.EventAction.DELETE,
WSDataSourceProperty.CONFIGURATION
);
log.info(String.format(
"Connection deleted: [info=%s, userId=%s]",
@@ -361,23 +330,6 @@ public class ConnectionControllerCE implements ConnectionController {
return project;
}
private WSDataSourceProperty getDatasourceEventProperty(
DataSourceDescriptor oldDataSource,
DBPDataSourceContainer dataSource
) {
if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) {
return WSDataSourceProperty.CONFIGURATION;
}
var nameChanged = !CommonUtils.equalObjects(oldDataSource.getName(), dataSource.getName());
var descriptionChanged = !CommonUtils.equalObjects(oldDataSource.getDescription(), dataSource.getDescription());
if (nameChanged && descriptionChanged) {
return WSDataSourceProperty.CONFIGURATION;
}
return nameChanged ? WSDataSourceProperty.NAME : WSDataSourceProperty.CONFIGURATION;
}
@NotNull
private WebConnectionInfo closeAndDeleteConnection(
@NotNull WebSession webSession,
@@ -529,10 +481,6 @@ public class ConnectionControllerCE implements ConnectionController {
public void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException {
}
public WebPropertyInfo[] getExternalInfo(WebSession session) {
return null;
}
private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException {
if (!DBWorkbench.isDistributed() && container.getDriver().getDriverLoader(container).needsExternalDependencies()) {
@@ -27,9 +27,11 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.graphql.GraphQLLoggerUtil;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -327,16 +329,26 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
}
}
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
}
HttpServletRequest request = this.env.getGraphQlContext().get("request");
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
setLogContext(method, args);
}
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, method, args);
if (method.getName() != null) {
log.debug("API > " + method.getName() + loggerMessage);
}
setLogContext(method, args);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
protected void setLogContext(Method method, Object[] args) {
@@ -27,7 +27,10 @@ import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import io.cloudbeaver.service.core.DBWServiceCore;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.*;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebCommonUtils;
import io.cloudbeaver.utils.WebConnectionFolderUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
@@ -41,14 +44,16 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.DBWNetworkHandler;
import org.jkiss.dbeaver.model.net.DBWTunnel;
import org.jkiss.dbeaver.model.net.ssh.SSHSession;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
@@ -56,7 +61,6 @@ import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import java.util.*;
@@ -393,15 +397,7 @@ public class WebServiceCore implements DBWServiceCore {
dataSourceRegistry.addDataSource(newDataSource);
dataSourceRegistry.checkForErrors();
WebConnectionInfo connectionInfo = project.addConnection(newDataSource);
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
connectionInfo.getId(),
WSConstants.EventAction.CREATE,
WSDataSourceProperty.CONFIGURATION
);
return connectionInfo;
return project.addConnection(newDataSource);
} catch (DBException e) {
throw new DBWebException("Error copying connection", e);
}
@@ -536,25 +532,18 @@ public class WebServiceCore implements DBWServiceCore {
) throws DBWebException {
WebConnectionFolderUtils.validateConnectionFolder(folderName);
session.addInfoMessage("Create new folder");
WebConnectionFolderInfo parentNode = null;
WebConnectionFolderInfo parentFolder = null;
try {
if (parentPath != null) {
parentNode = WebConnectionFolderUtils.getFolderInfo(session, projectId, parentPath);
parentFolder = WebConnectionFolderUtils.getFolderInfo(session, projectId, parentPath);
}
WebProjectImpl project = getProjectById(session, projectId);
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode,
folderName,
sessionRegistry);
WebConnectionFolderInfo folderInfo = new WebConnectionFolderInfo(session, newFolder);
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
WebEventUtils.addNavigatorNodeUpdatedEvent(
session.getProjectById(projectId),
session,
DBNLocalFolder.makeLocalFolderItemPath(newFolder),
WSConstants.EventAction.CREATE
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
DBPDataSourceFolder newFolder = registry.addFolder(
parentFolder == null ? null : parentFolder.getDataSourceFolder(), folderName
);
return folderInfo;
WebServiceUtils.refreshDatabases(session, projectId);
return new WebConnectionFolderInfo(session, newFolder);
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
@@ -565,26 +554,12 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession session,
@Nullable String projectId,
@NotNull String folderPath,
@NotNull String newName
@NotNull String newPath
) throws DBWebException {
WebConnectionFolderUtils.validateConnectionFolder(newName);
WebConnectionFolderUtils.validateConnectionFolder(newPath);
WebConnectionFolderInfo folderInfo = WebConnectionFolderUtils.getFolderInfo(session, projectId, folderPath);
var oldFolderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder());
folderInfo.getDataSourceFolder().setName(newName);
var newFolderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder());
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
WebEventUtils.addNavigatorNodeUpdatedEvent(
session.getProjectById(projectId),
session,
oldFolderNode,
WSConstants.EventAction.DELETE
);
WebEventUtils.addNavigatorNodeUpdatedEvent(
session.getProjectById(projectId),
session,
newFolderNode,
WSConstants.EventAction.CREATE
);
folderInfo.getDataSourceFolder().setName(newPath);
WebServiceUtils.refreshDatabases(session, projectId);
return folderInfo;
}
@@ -599,17 +574,10 @@ public class WebServiceCore implements DBWServiceCore {
if (folder.getDataSourceRegistry().getProject() != project) {
throw new DBWebException("Global folder '" + folderInfo.getId() + "' cannot be deleted");
}
var folderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder());
session.addInfoMessage("Delete folder");
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
sessionRegistry.removeFolder(folderInfo.getDataSourceFolder(), false);
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
WebEventUtils.addNavigatorNodeUpdatedEvent(
session.getProjectById(projectId),
session,
folderNode,
WSConstants.EventAction.DELETE
);
WebServiceUtils.refreshDatabases(session, projectId);
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
@@ -624,12 +592,6 @@ public class WebServiceCore implements DBWServiceCore {
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer());
dataSourceDescriptor.setNavigatorSettings(settings);
dataSourceDescriptor.persistConfiguration();
WebEventUtils.addDataSourceUpdatedEvent(
webSession.getProjectById(projectId),
webSession,
id,
WSConstants.EventAction.UPDATE,
WSDataSourceProperty.CONFIGURATION);
return connectionInfo;
}
@@ -656,16 +618,4 @@ public class WebServiceCore implements DBWServiceCore {
}
return project;
}
private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException {
if (!DBWorkbench.isDistributed() && container.getDriver().getDriverLoader(container).needsExternalDependencies()) {
throwDriverNotFoundException(container);
}
}
@NotNull
private static String throwDriverNotFoundException(@NotNull DBPDataSourceContainer container) throws DBWebException {
throw new DBWebException("Driver files for %s are not found. Please ask the administrator to download it."
.formatted(container.getDriver().getName()));
}
}
@@ -55,7 +55,6 @@ 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;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
@@ -461,28 +460,10 @@ public class WebServiceNavigator implements DBWServiceNavigator {
if (siblings.contains(newName)) {
throw new DBWebException("Name " + newName + " is unavailable or invalid");
}
var oldNodePath = node.getNodeItemPath();
node.rename(session.getProgressMonitor(), newName);
var newNodePath = node.getNodeItemPath();
addNavigatorNodeMoveEvent(session, node, oldNodePath, newNodePath);
return node.getName();
}
private void addNavigatorNodeMoveEvent(@NotNull WebSession session, DBNNode node, String oldNodePath, String newNodePath) {
WebEventUtils.addNavigatorNodeUpdatedEvent(
node.getOwnerProject(),
session,
oldNodePath,
WSConstants.EventAction.DELETE
);
WebEventUtils.addNavigatorNodeUpdatedEvent(
node.getOwnerProject(),
session,
newNodePath,
WSConstants.EventAction.CREATE
);
}
@NotNull
private String renameRmResourceNode(@NotNull WebSession session, DBNNode node, @NotNull String newName) throws DBException {
if (newName.contains("/") || newName.contains("\\")) {
@@ -570,14 +551,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
throw e;
}
} else if (node instanceof DBNLocalFolder) {
var nodePath = node.getNodeItemPath();
node.getOwnerProject().getDataSourceRegistry().removeFolder(((DBNLocalFolder) node).getFolder(), false);
WebEventUtils.addNavigatorNodeUpdatedEvent(
session.getProjectById(projectId),
session,
nodePath,
WSConstants.EventAction.DELETE
);
} else if (node instanceof DBNResourceManagerResource) {
DBNResourceManagerResource rmResource = ((DBNResourceManagerResource) node);
String resourceProjectId = rmResource.getResourceProject().getId();
@@ -592,7 +566,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
}
}
if (containsFolderNodes) {
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
WebServiceUtils.refreshDatabases(session, projectId);
}
return nodes.size();
@@ -645,15 +619,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
folder = localFolderNode.getFolder();
}
dataSourceNode.moveToFolder(folderNode.getOwnerProject(), folder);
node.getOwnerProject().getDataSourceRegistry().updateDataSource(
dataSourceNode.getDataSourceContainer());
WebEventUtils.addDataSourceUpdatedEvent(
node.getOwnerProject(),
session,
dataSourceNode.getDataSourceContainer().getId(),
WSConstants.EventAction.UPDATE,
WSDataSourceProperty.CONFIGURATION
);
node.getOwnerProject().getDataSourceRegistry().updateDataSource(dataSourceNode.getDataSourceContainer());
} else if (node instanceof DBNLocalFolder dbnLocalFolder) {
DBPDataSourceFolder parentFolder = null;
if (folderNode instanceof DBNLocalFolder parentFolderNode) {
@@ -667,15 +633,12 @@ public class WebServiceNavigator implements DBWServiceNavigator {
throw new DBWebException("Node " + folderNodePath + " contains folder with name '" + node.getName() + "'");
}
}
var oldNodePath = node.getNodeItemPath();
node.getOwnerProject().getDataSourceRegistry().moveFolder(
dbnLocalFolder.getFolder().getFolderPath(),
dbnLocalFolder.generateNewFolderPath(parentFolder, dbnLocalFolder.getNodeDisplayName())
);
node.getOwnerProject().getDataSourceRegistry().checkForErrors();
var newNodePath = node.getNodeItemPath();
WebServiceUtils.updateConfigAndRefreshDatabases(session, node.getOwnerProject().getId());
addNavigatorNodeMoveEvent(session, node, oldNodePath, newNodePath);
WebServiceUtils.refreshDatabases(session, node.getOwnerProject().getId());
} else if (node instanceof DBNResourceManagerResource) {
boolean rmNewNode = folderNode instanceof DBNAbstractResourceManagerNode;
DBNResourceManagerResource rmOldNode = (DBNResourceManagerResource) node;
@@ -103,7 +103,7 @@ public class WebSQLCompletionContext implements SQLCompletionContext {
@Override
public boolean isSortAlphabetically() {
return false;
return true;
}
@Override
@@ -67,4 +67,8 @@ public class WebSQLCompletionProposal {
return null;
}
public CompletionProposalBase getProposal() {
return proposal;
}
}
@@ -55,6 +55,7 @@ import org.jkiss.dbeaver.model.sql.parser.SQLParserContext;
import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorConfigurationRegistry;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorDescriptor;
import org.jkiss.dbeaver.model.sql.semantics.completion.SQLCompletionProposalComparator;
import org.jkiss.dbeaver.model.sql.semantics.completion.SQLQueryCompletionAnalyzer;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSObject;
@@ -200,6 +201,11 @@ public class WebServiceSQL implements DBWServiceSQL {
for (int i = 0; i < proposals.size(); i++) {
result[i] = new WebSQLCompletionProposal(proposals.get(i));
}
SQLCompletionProposalComparator sqlCompletionProposalComparator = new SQLCompletionProposalComparator(
completionContext.isSortAlphabetically(),
completionContext.isSearchInsideNames()
);
Arrays.sort(result, (o1, o2) -> sqlCompletionProposalComparator.compare(o1.getProposal(), o2.getProposal()));
return result;
} catch (Exception e) {
throw new DBWebException("Error processing SQL proposals", e);
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.128.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 1.0.130.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.128-SNAPSHOT</version>
<version>1.0.130-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -494,7 +494,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@NotNull WebSession webSession,
@Nullable String providerId
) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
String origin = ServletAppUtils.getOriginFromRequest(request);
List<WebAuthProviderConfiguration> result = new ArrayList<>();
for (SMAuthProviderCustomConfiguration cfg : CBApplication.getInstance().getAppConfiguration().getAuthCustomConfigurations()) {
if (providerId != null && !providerId.equals(cfg.getProvider())) {
@@ -544,7 +544,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
providerConfig.getProvider(),
webSession.getUserId()
));
return new WebAuthProviderConfiguration(authProvider, providerConfig, ServletAppUtils.getOriginFromRequestOrThrow(request));
return new WebAuthProviderConfiguration(authProvider, providerConfig, ServletAppUtils.getOriginFromRequest(request));
}
@Override
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.128.qualifier
Bundle-Release-Date: 20250901
Bundle-Version: 1.0.130.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.128-SNAPSHOT</version>
<version>1.0.130-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -111,7 +111,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
try {
Map<String, Object> authParameters = new HashMap<>();
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequestOrThrow(httpRequest));
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequest(httpRequest));
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
@@ -227,7 +227,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
List<WebAuthInfo> removedInfos = webSession.removeAuthInfo(providerId);
List<String> logoutUrls = new ArrayList<>();
var cbApp = CBApplication.getInstance();
String origin = ServletAppUtils.getOriginFromRequestOrThrow(httpRequest);
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
.getInstance() instanceof SMSignOutLinkProvider provider
@@ -297,7 +297,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException {
String origin = ServletAppUtils.getOriginFromRequestOrThrow(request);
String origin = ServletAppUtils.getOriginFromRequest(request);
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream()
.map(descriptor -> new WebAuthProviderInfo(descriptor, origin))

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