Compare commits

...
Author SHA1 Message Date
Evgenia f5103cab9e Merge branch 'devel' into dbeaver/9864-link-editor 2026-07-21 11:20:26 +02:00
alex af42d7f50a dbeaver/pro#9910 check key also (#4469) 2026-07-20 15:55:01 +02:00
Daria Marutkina d083ac1330 dbeaver/pro#9879 Update README file (#4472)
Added changelog for version 26.1.3, including updates on data editor, general improvements, data transfer enhancements, accessibility features, security fixes, and database driver updates.
2026-07-20 13:21:53 +02:00
naumov ce309e2061 dbeaver/pro#9864 fallback to connection on the link menu level 2026-07-16 12:04:22 +02:00
Serge Rider cdb450dfde dbeaver/pro#7329 Navigator view actions model refactoring (get rid of UI).
Git service and navigator web service code style.
2026-07-15 12:01:30 +02:00
dbeaver-devops d0d9ee19ab dbeaver/pro#9879 26.1.4 version bump 2026-07-14 19:24:11 +02:00
9327ce0ac2 dbeaver/pro#9522 integrate profile selector to connection form (#4440)
* dbeaver/pro#9522 integrate profile selector to connection form

* dbeaver/pro#9522 handles edge cases

* dbeaver/pro#9522 uses rest instead of default getter

* dbeaver/pro#9522 adds preprocessing to handle null/undefined differences arrived from backend for both: connection form network handlers & profile form network handlers

* dbeaver/pro#9522 eslint fix

* dbeaver/pro#9522 refactors profiles

* dbeaver/pro#9522 removes preprocess

* dbeaver/pro#9522 adds header & footer items

* dbeaver/pro#9522 truncates items values

* dbeaver/pro#9522 fix retrieving creds from profiles

* dbeaver/pro#9522 fix test temporary connection

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-07-14 18:50:59 +02:00
1fb9dd7b5a dbeaver/pro#9723 Fix UI styles for improved accessibility and consistency (#4459)
* dbeaver/pro#9723 feat: add focus-visible styles for improved accessibility

* dbeaver/pro#9723 fix: adjust gutter size in ComboboxPopover for consistent spacing

* dbeaver/pro#9723 fix: update button outline offset for consistency

* dbeaver/pro#9723 fix: update hover opacity

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-14 18:37:55 +02:00
alexandDaria Marutkina e82cac479b dbeaver/pro#9861 pass null as valid filter (#4461)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-14 17:44:07 +02:00
bf6afb0340 dbeaver/pro#9712 Databend driver updated to 0.4.8 (#4453)
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-14 16:13:26 +02:00
4779c3b405 dbeaver/pro#9000 adds API token settings (#4455)
* dbeaver/pro#9000 adds API token settings

* dbeaver/pro#9000 adds save extend instead

* dbeaver/pro#9000 adds observable to the schema

* dbeaver/pro#9000 adds LooseObject in favor of server config part state extension

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-14 12:15:43 +02:00
31ee01b6fa dbeaver/pro#9614 add predefined param for connection type (#4454)
* dbeaver/pro#9614 add predefined param for connection type

* dbeaver/pro#9614 add connection type selector

* dbeaver/pro#9614 make menu indicator more visible

* dbeaver/pro#9614 add extra colors

* dbeaver/pro#9614 outdate connections

* dbeaver/pro#9614 load types in bootstrap phrase

* dbeaver/pro#9614 always use useConnectionTypeColor

* dbeaver/pro#9614 remove unused import

* dbeaver/pro#9614 add useConnectionTypeLoader hook

* dbeaver/pro#9614 add default hint

* dbeaver/pro#9614 do not wrap arg:item by default

* dbeaver/pro#9614 use predefined unset for all types

* dbeaver/pro#9614 use common mapped unset color

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-13 22:11:10 +02:00
025944a867 dbeaver/pro#8947 user provisioning return user ids (#4429)
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-13 16:00:32 +02:00
Serge Rider caee9914b8 dbeaver/pro#9851 Refactor data transfer producer. Smart mapping bug fix. (#4458) 2026-07-13 15:43:53 +02:00
sergeyteleshevandEvgenia 6e2b1b7a17 dbeaver/pro#8849 fixes spacing for bind parameters popup (#4456)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-09 19:22:41 +02:00
sergeyteleshevandDaria Marutkina a379c2d7ba dbeaver/pro#9755 adds frontend cleanup for js artifacts before each app build (#4450)
* dbeaver/pro#9755 adds frontend cleanup for js artifacts before each app build

* dbeaver/pro#9775 adds x to log (revert it)

* dbeaver/pro#9775 adds clear commands to build frontend script

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-08 17:44:33 +02:00
sergeyteleshevandDaria Marutkina 8b1054a148 dbeaver/pro#9775 adds unit test for getLocalizedDisplayName (#4451)
* dbeaver/pro#9775 adds unit test for getLocalizedDisplayName

* dbeaver/pro#9775 adds unit test for core utils

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-08 17:44:21 +02:00
Ruslan MusaevandEvgenia 7fdc8f7e61 dbeaver/pro#9714 fix ldap injection (#4441)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-08 10:21:15 +02:00
Sychev AndreyandEvgenia 32bebd4bad dbeaver/pro#9303 feat: update edge color variable in CSS for execution plan (#4387)
* dbeaver/pro#9303 feat: update edge color variable in CSS for execution plan

* dbeaver/pro#9303 feat: update node cost bar background and add theme-light styles for edges and captions

* dbeaver/pro#9303 feat: update edge color in execution plan CSS

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-08 09:47:46 +02:00
Serge RiderandAinur 4221ea7fe0 dbeaver/pro#8757 Test connection permissions required (#4434)
* dbeaver/pro#8757 Test connection permissions required (the same as createConnection)

* dbeaver/pro#8757 Anno added

* dbeaver/pro#8757 restrict private project use for gql

* dbeaver/pro#8757 change error message

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
2026-07-07 14:10:26 +02:00
sergeyteleshevandDaria Marutkina af11aaa8fe dbeaver/cloudbeaver#3815 upgrades lib to version where it works (#4165)
* dbeaver/cloudbeaver#3815 upgrades lib to version where it works

* dbeaver/pro#3815 updates react-hotkeys-hook lib

* dbeaver/pro#3815 fixes build

* dbeaver/pro#3815 fixes case dependant hotkeys activation

* dbeaver/pro#3815 allows textbox form tag to support sql editor

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-07 14:09:27 +02:00
5c58343a10 dbeaver/pro#8402 feat: enable checkbox selection via keyboard in Team… (#4445)
* dbeaver/pro#8402 feat: enable checkbox selection via keyboard in Teams and Users tables

* dbeaver/pro#8402 feat: enable checkbox selection via keyboard and improve accessibility in tables

fix checkbox and header cell keyboard events

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-07-07 10:59:16 +02:00
Serge Rider a565beb777 dbeaver/pro#9764 Fix launch parameters for Java 25 (#4448) 2026-07-06 16:59:22 +02:00
9df99fb62e dbeaver/pro#8427 feat: enable default fold keymap in CodeMirror (#4446)
* dbeaver/pro#8427 feat: enable default fold keymap in CodeMirror

* dbeaver/pro#8427 feat: add SQL editor shortcuts for folding and unfolding code blocks

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-07-06 14:31:26 +02:00
Serge RiderandMashaKorax b3127b9aa7 dbeaver/pro#9682 AI config profiles (#4437)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-07-06 13:49:35 +02:00
Daria Marutkina f8677a0689 dbeaver/pro#9745 Update README file (#4447)
Updated changelog for version 26.1.2, detailing new features, bug fixes
2026-07-06 11:32:53 +02:00
Ruslan Musaev f22a936507 dbeaver/pro#9749 fix sso login npe (#4443)
* dbeaver/pro#9749 fix sso login npe

* dbeaver/pro#9749 fix sso login npe
2026-07-01 20:05:26 +02:00
Serge Rider b5858637d6 dbeaver/pro#9222 Annotations and permissions 2026-07-01 09:48:04 +02:00
serge-rider 475c8129fc dbeaver/pro#4320 Annotations 2026-06-30 21:55:37 +02:00
dbeaver-devops 50d846d087 dbeaver/pro#9745 26.1.3 version bump 2026-06-30 19:42:04 +02:00
Ainur 16bd3ca575 dbeaver/pro#9612 api for connection types (#4426)
* dbeaver/pro#9612 api for connection types

* dbeaver/pro#9612 add more fields for connection type info

* dbeaver/pro#9612 renaming and event handling

* dbeaver/pro#9612 fix doc and add all inputs
2026-06-30 19:15:46 +02:00
Alexander SkoblikovandMashaKorax 927184e009 dbeaver/pro#3833 fix bug with "null" origin header (#4395)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-06-30 18:27:18 +02:00
d4d9081936 dbeaver/pro#9652 refactor ref api (#4412)
* dbeaver/pro#9652 refactor ref api

* dbeaver/pro#9652 refactor ref api

* dbeaver/pro#9652 refactor ref api

* dbeaver/pro#9652 refactor ref api

* dbeaver/pro#9652 remove references

* dbeaver/pro#9652 add anyConstraint

* dbeaver/pro#9652 add references panel

* dbeaver/pro#9652 remove logs

* dbeaver/pro#9652 fix attr position

* dbeaver/pro#9652 fix review

* dbeaver/pro#9652 change naming

* dbeaver/pro#9652 fallback to custom if composite and more than 1 row

* dbeaver/pro#9652 add indicator

* dbeaver/pro#9652 show proper error

* dbeaver/pro#9652 fix review, add targetColumnIndex

* dbeaver/pro#9652 fix targetColumnIndex

* dbeaver/pro#9652 pass target index

* dbeaver/pro#9652 add icons

* dbeaver/pro#9652 check prev constaints, decrease blicking

* dbeaver/pro#9652 fix constraint overwriting

* dbeaver/pro#9652 pass anyConstraint flag to export

* dbeaver/pro#9652 disable references for editors and viewers

* dbeaver/pro#9652 update icons

* dbeaver/pro#9652 show placeholder instead of loader

* dbeaver/pro#9652 show combobox

---------

Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-06-30 17:57:40 +02:00
a0bcfba73d dbeaver/pro#9639 load driver properties fix (#4420)
* dbeaver/pro#9639 load driver properties fix

* dbeaver/pro#9639 pass connection config to get driver properties

* dbeaver/pro#9639 execute format action before loading properties

* dbeaver/pro#9639 do not reset properties if tab is selected

* dbeaver/pro#9639 fix import

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-30 17:38:36 +02:00
Sychev Andreyandmr-anton-t 09f4828006 dbeaver/pro#9704 [CB] Fixes outlines for several components (#4431)
* dbeaver/pro#9704 fix: adjust combobox and select control outline properties

* dbeaver/pro#9704 fix: add control outline color and width for checkbox and radio components

* dbeaver/pro#9704 fix: set outline color and width for disclosure component

* dbeaver/pro#9704 refactor: add text overflow handling for combobox item values

remove tw:truncate because it adds hidden text-sm font-size

* dbeaver/pro#9704 fix: update combobox popover and item classes

* dbeaver/pro#9704 fix: correct portal handling in SelectPopover component

* dbeaver/pro#9704 fix: ensure correct portal handling in ComboboxPopover component

* dbeaver/pro#9704 refactor: rename ComboboxType to IComboboxType for consistency

* dbeaver/pro#9704 fix: line-height for combobox items

* dbeaver/pro#9704 style: remove unnecessary letter-spacing from combobox item

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-30 17:17:54 +02:00
alexandDaria Marutkina cde64e255b dbeaver/pro#9586 adjust validation (#4427)
* dbeaver/pro#9586 let editing of loaded truncated values

* Revert "dbeaver/pro#9586 let editing of loaded truncated values"

This reverts commit 6b412ef57c.

* dbeaver/pro#9586 adjust quota validation check

* dbeaver/pro#9586 show limit amount

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-06-30 16:42:01 +02:00
7b134cef81 dbeaver/pro#9526 adds profile form (#4390)
* dbeaver/pro#9526 adds profile form base functionality

* dbeaver/pro#9526 adds profiles form

* dbeaver/pro#9526 moves SSHForm to connection-network-handlers to reuse it for admin settings in future without extra deps

* dbeaver/pro#9526 moves ssh files to connection-network-handlers-plugin

* pr fixes

* moves enable form switch inside SSHForm

* dbeaver/pro#9526 shares the rest logic for configuring the ssh with profiles form as well

* dbeaver/pro#9526 cleanup

* dbeaver/pro#9526 adds go back to profiles arrow action

* dbeaver/pro#9526 small ui adjustments

* dbeaver/pro#9618 pr fixes

* dbeaver/pro#9526 creates ProjectInfoOptionsPanelService to incapsulate opening logic with tabId

* dbeaver/pro#9526 adds core-network-handlers plugin

* dbeaver/pro#9526 moves ssh network handler layout to core-network-handlers conig + adds TODO to register it to connection form

* dbeaver/pro#9526 moves registration of connection SSH network handler to plugin-connection-network-handler (from plugin-connection)

* dbeaver/pro#9526 moves registration of connection SSL network handler to plugin-connection-network-handler (from plugin-connection)

* dbeaver/pro#9526 migrates localizations among the plugins + adds missing translations

* dbeaver/pro#9526 cleanup

* dbeaver/pro#9526 fixes build

* dbeaver/pro#9526 core-network-handlers -> plugin-network-handlers

* dbeaver/pro#9526 removes license from locale files

* dbeaver/pro#9526 CONNECTION_NETWORK_HANDLER_SCHEMA -> NETWORK_HANDLER_SCHEMA

* dbeaver/pro#9526 saveChanges interface update

* dbeaver/pro#9526 refactors core -> plugin tokens

* dbeaver/pro#9526 applies copilot suggestions

* dbeaver/pro#9526 pr fixes

* dbeaver/pro#9526 moves test tunnel functionality inside ssh form

* dbeaver/pro#9526 handle network profiles changes

* dbeaver/pro#9526 cleanup

* dbeaver/pro#9526 fixes after review

* dbeaver/pro#9526 fix sending event for profiles

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
2026-06-30 16:41:10 +02:00
Ruslan Musaev fde7502df8 dbeaver/pro#9713 fix event filter for rotating session (#4436) 2026-06-30 12:36:31 +02:00
Sychev Andrey 955b28c520 dbeaver/pro#9699 fix: reorder items in sql toolbar (#4433) 2026-06-30 12:29:58 +02:00
Ainur 5cfc37e26f dbeaver/pro#9481 save api token max duration fix (#4435) 2026-06-30 12:28:24 +02:00
Ruslan Musaevandsergeyteleshev 42cb6b3a9d dbeaver/pro#9022 fix error file saving (#4424)
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-06-26 12:16:44 +02:00
Serge Riderandkseniaguzeeva ad245e9f4c dbeaver/pro#9700 Save RM datasources correctly (#4425)
* dbeaver/pro#9700 Save RM datasources correctly

* dbeaver/pro#9700 Fix empty collection parameters pass over remote client

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-06-25 14:35:26 +02:00
08260e6a55 dbeaver/cloudbeaver#4249 added session rotation (#4274) (#4331)
* dbeaver/cloudbeaver#4249 added session rotation (#4274)

* dbeaver/cloudbeaver#4249 added session rotation

* dbeaver/pro#4249 update web session id

* dbeaver/pro#4249 remove log

* dbeaver/pro#4249 remove unused params

* dbeaver/pro#4249 revert non-related changes

* dbeaver/cloudbeaver#4249 rotate session before login

* dbeaver/cloudbeaver#4249 fix code style

* dbeaver/cloudbeaver#4249 rotate only for anon users

* dbeaver/cloudbeaver#4249 added notnulls

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>

* dbeaver/pro#4249 fix websocket for session rotation

---------

Co-authored-by: Dmitrii Barnukov <29237913+Nexus6v2@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ruslan <pupkinvasya7000@gmail.com>
2026-06-25 07:06:38 +02:00
Serge Rider aea12e9fed dbeaver/pro#9700 Save RM datasources correctly 2026-06-24 17:48:29 +02:00
Ruslan MusaevandDaria Marutkina caef729a8d dbeaver/pro#9599 fix allows unauthorized server file stream (#4411)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-06-24 15:29:07 +02:00
12030822ea dbeaver/pro#9502 [CB] move chart settings export dialogs to the widget (#4399)
* dbeaver/pro#9502 feat: allow importing css files from core-blocks package

Plugins that build UI directly on ui-kit components need the CloudBeaver theme css for these components without importing the components themselves. Expose css files through package exports so a plugin can load them as a side effect.

* dbeaver/pro#9502 fix: update focus-visible styles for select component

* dbeaver/pro#9502 feat: add tokens for combobox items

* dbeaver/pro#9502 feat: load Select/Combobox base styles from core-blocks

ui-kit controls reuse these styles, but the CE wrapper components are lazy, so the CSS only shipped in the lazy chunk, we need those styles for widgets that use UI-kit controls

* dbeaver/pro#9502 feat: enhance Select component with value display styling

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-24 12:54:46 +02:00
Mikhailov GrigoriiandSerge Rider 97cf62d333 dbeaver/dbeaver-devops#2660 Added permissions read on contents pull-requests (#4422)
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-06-24 05:01:41 +02:00
8240c92247 dbeaver/pro#8796 [CB] create a separate plugin for the execution plan (#4406)
* dbeaver/pro#8796 refactor: extract Execution Plan into separate plugin

Move all Execution Plan logic out of plugin-sql-editor into a new plugin-sql-editor-execution-plan. plugin-sql-editor no longer knows about execution plan and instead exposes generic extension points

Execution plan tab data now lives in-memory in SqlExecutionPlanService instead of the persisted editor state schema (these tabs are session-only).

* dbeaver/pro#8796 feat: add naming convention rule for exported interfaces

* dbeaver/pro#8796 fix: update js-helpers dependency version in yarn.lock

* dbeaver/pro#8796 refactor: reorder register method for better readability

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-23 21:51:29 +02:00
sergeyteleshevandEvgenia 62a194c1b0 dbeaver/pro#7510 adds last login info to users tables (#4421)
* dbeaver/pro#7510 adds last login info to users tables

* dbeaver/pro#7510 shows only date

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-06-23 21:46:51 +02:00
sergeyteleshevandEvgenia 36e954c440 dbeaver/pro#9664 removes undici CVE (#4415)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-06-23 13:05:38 +02:00
Sychev AndreyandEvgenia a674c183d5 dbeaver/pro#5015 [CB] Ability to use keyboard to enabledisable checkboxes in the properties tab (#4416)
* dbeaver/pro#5015 feat: enable keyboard navigation for context menu in ObjectMenuCell

* dbeaver/pro#5015 feat: enable keyboard activation for context menu in ObjectMenuCell

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-06-23 12:51:04 +02:00
Sychev Andrey 4bab1025d7 dbeaver/pro#9660 fix: selection for search panel inputs (#4418) 2026-06-23 12:49:27 +02:00
Serge Riderandmr-anton-t 7d21186158 dbeaver/pro#9266 Refactor workspace path init and change (#4409)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-22 16:54:26 +02:00
eb5f89dae3 dbeaver/pro#9644 Data filters thread safety (#4407)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-06-22 16:06:37 +02:00
70292cb9c0 dbeaver/pro#9588 remove top margin (#4405)
* dbeaver/pro#9588 remove top margin

* dbeaver/pro#9588 fix: move margin from indicator to checkbox itself

add multiline checkbox story for alignment with text

* Revert "dbeaver/pro#9588 remove top margin"

This reverts commit 0b25ed6f00.

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-06-22 14:38:20 +02:00
Evgenia 950f45833a dbeaver/pro#9630 Update README file CB 26.1.1 (#4414)
Added changelog for version 26.1.1
2026-06-22 12:39:55 +02:00
451 changed files with 5632 additions and 1769 deletions
+7 -4
View File
@@ -30,10 +30,13 @@ jobs:
uses: ./.github/workflows/frontend-build.yml
secrets: inherit
# lint-server:
# name: Server
# uses: dbeaver/dbeaver-common/.github/workflows/java-checkstyle.yml@devel
# secrets: inherit
lint-server:
name: Server
uses: dbeaver/dbeaver-common/.github/workflows/java-checkstyle.yml@devel
secrets: inherit
permissions:
contents: read
pull-requests: read
lint-frontend:
name: Frontend
+1 -1
View File
@@ -48,6 +48,7 @@
"-Declipse.pde.launch=true",
"-XstartOnFirstThread",
"-Dfile.encoding=UTF-8",
"--enable-native-access=ALL-UNNAMED",
"--add-modules=ALL-DEFAULT",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
@@ -64,7 +65,6 @@
"--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED",
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
"--add-opens=java.base/sun.security.ssl=ALL-UNNAMED",
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
"--add-opens=java.base/sun.security.util=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
+46
View File
@@ -28,6 +28,52 @@ You can see a live demo of CloudBeaver server here: https://demo.cloudbeaver.io
## Changelog
### 26.1.3 2026-07-20
- Data Editor:
- Fixed copying of large JSON values in the Data Editor - now the full value can be copied and transferred.
- Updated the Reference panel to show only relevant rows when a foreign key is null.
- General:
- Added connection types to visually distinguish database environments using background colors across the Database Navigator and application UI. Users with edit permissions can assign built-in environment types color-coded configurations.
- Data Transfer:
- Improved memory usage when importing large CSV files.
- Fixed CSV export with multi-character quotes and improved quote/delimiter validation
- Accessibility:
- Enabled the use of the Enter or space keys to toggle checkboxes in the administration tabs and the metadata viewer.
- Added the ability to expand and collapse code block elements, including JSON, using the keyboard.
- Fixed shortcuts behavior for AZERTY keyboards. Now shortcuts like Ctrl/Cmd + Z work correctly.
- Security:
- Resolved an authorization vulnerability when testing connections by enforcing strict permission checks. This mitigation prevented unauthenticated users from exploiting JDBC requests to read arbitrary server files.
- Resolved an LDAP injection vulnerability by properly escaping user input in the authentication username field, preventing attackers from manipulating search filters, bypassing access restrictions, and enumerating directory users.
- Fixed the high vulnerability (GHSA-gv7w-rqvm-qjhr) in the esbuild library. The library was updated to version 0.28.1.
- Fixed the high vulnerability (CVE-2026-12143) in the form-data library. The library was updated to version 4.0.6.
- Databases and drivers:
- The Databend driver was updated to version 0.4.8.
- Fixed an issue related to the display of metadata in the navigation tree for DB2, Firebird, and MySQL.
### 26.1.2 2026-07-06
- Data Editor:
- Added the References panel showing related records from other tables connected by foreign keys. It helps to explore table relationships directly in the Data editor.
- Resolved an issue where the Data Editor ignored the text preview's maximum length quota. Increasing this quota now enables the ability to edit large values.
- General:
- Added the Last Login time to the user form and the user table in the administration part.
- Fixed the bug with the inability to select entered text in the Find and Replace panel in editors.
- Accessibility:
- Added the ability to use the Enter or Space key to turn checkboxes on/off in the connection properties tab.
- Security:
- Fixed CVE-2026-55449. Resolved a local file disclosure vulnerability by enforcing strict validation of file identifiers and path containment during data updates.
- Updated session handling to prevent session fixation vulnerabilities. The application generates new session identifiers upon login.
- Fixed the high vulnerability (CVE-2026-12151) in the undici library. The library was updated to version 6.27.0.
### 26.1.1 2026-06-22
- General: Added a "Project Info" tab containing the project description to the project context menu.
- Security:
- Fixed a cross-site scripting vulnerability caused by unsanitized user input in the web interface.
- Resolved a security flaw related to user-controlled file paths by adding strict validation to prevent unauthorized resource access.
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4).
### 26.1.0 2026-06-01
### Changes since 26.0.0:
+9 -1
View File
@@ -8,7 +8,15 @@ mkdir -p ./cloudbeaver/web
cd ../../cloudbeaver/webapp
yarn install --immutable
cd ./packages/product-default
yarn clear
cd common-react
yarn clear
cd ../common-typescript
yarn clear
cd ../packages/product-default
yarn run bundle
if [[ "$?" -ne 0 ]] ; then
+1 -1
View File
@@ -17,6 +17,7 @@ If Not Defined JAVA_OPTS (
java %JAVA_OPTS% ^
-Dfile.encoding=UTF-8 ^
--enable-native-access=ALL-UNNAMED ^
--add-modules=ALL-DEFAULT ^
--add-opens=java.base/java.io=ALL-UNNAMED ^
--add-opens=java.base/java.lang=ALL-UNNAMED ^
@@ -33,7 +34,6 @@ java %JAVA_OPTS% ^
--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED ^
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED ^
--add-opens=java.base/sun.security.ssl=ALL-UNNAMED ^
--add-opens=java.base/sun.security.action=ALL-UNNAMED ^
--add-opens=java.base/sun.security.util=ALL-UNNAMED ^
--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED ^
--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED ^
+1 -1
View File
@@ -11,6 +11,7 @@ echo "Starting Cloudbeaver Server"
exec java ${JAVA_OPTS} \
-Dfile.encoding=UTF-8 \
--enable-native-access=ALL-UNNAMED \
--add-modules=ALL-DEFAULT \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
@@ -27,7 +28,6 @@ exec java ${JAVA_OPTS} \
--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED \
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/sun.security.ssl=ALL-UNNAMED \
--add-opens=java.base/sun.security.action=ALL-UNNAMED \
--add-opens=java.base/sun.security.util=ALL-UNNAMED \
--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED \
--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED \
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
Bundle-Version: 1.0.13.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.15.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model.cli</artifactId>
<version>1.0.13-SNAPSHOT</version>
<version>1.0.15-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -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.104.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.106.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.104-SNAPSHOT</version>
<version>1.0.106-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -367,6 +367,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
dataSourceRegistry.initializeDataSources();
}
@Nullable
@Override
public DataSourceParseResults loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@@ -382,6 +383,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
dataSourceRegistry.saveDataSources();
}
@NotNull
@Override
public DataSourceConfigurationManager getConfigurationManager() {
return dataSourceRegistry.getConfigurationManager();
@@ -357,6 +357,13 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
return getInputConfigHandler(configInput).createDataSourceContainer();
}
public void updateDataSourceContainerFromInput(
@NotNull WebConnectionConfig configInput,
@NotNull DataSourceDescriptor dataSource
) throws DBWebException {
getInputConfigHandler(configInput).updateDataSource(dataSource);
}
@NotNull
public WebConnectionConfig getConnectionConfigInput(@Nullable Map<String, Object> configMap) {
return new WebConnectionConfig(configMap == null ? Map.of() : configMap);
@@ -65,6 +65,7 @@ public class WebConnectionConfig {
private Boolean defaultAutoCommit;
private String defaultCatalogName;
private String defaultSchemaName;
private String connectionType;
@NotNull
private Map<String, String> defaultUserPreferences = new LinkedHashMap<>();
@@ -120,6 +121,7 @@ public class WebConnectionConfig {
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
}
connectionType = JSONUtils.getString(params, "connectionType");
}
@Property
@@ -381,4 +383,13 @@ public class WebConnectionConfig {
public void setDefaultUserPreferences(@NotNull Map<String, String> defaultUserPreferences) {
this.defaultUserPreferences = defaultUserPreferences;
}
@Nullable
public String getConnectionType() {
return connectionType;
}
public void setConnectionType(@Nullable String connectionType) {
this.connectionType = connectionType;
}
}
@@ -586,6 +586,11 @@ public class WebConnectionInfo {
return tools;
}
@NotNull
public String getConnectionType() {
return dataSourceContainer.getConnectionConfiguration().getConnectionType().getId();
}
/**
* Updates param that checks whether credentials were saved only in session.
*/
@@ -37,7 +37,6 @@ import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import org.jkiss.dbeaver.runtime.IVariableResolver;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
@@ -89,12 +88,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return new SharedFileLockManager(getApplicationInstanceId());
}
@Nullable
@Override
public Path getDefaultWorkingFolder() {
return getServerConfigurationController().getWorkspacePath();
}
@Override
public boolean isHeadlessMode() {
return true;
@@ -192,20 +185,20 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
@SuppressWarnings("unchecked")
public static void patchConfigurationWithProperties(
Map<String, Object> configProps, IVariableResolver varResolver
@NotNull Map<String, Object> configProps,
@NotNull IVariableResolver varResolver
) {
for (Map.Entry<String, Object> entry : configProps.entrySet()) {
Object propValue = entry.getValue();
if (propValue instanceof String) {
entry.setValue(GeneralUtils.replaceVariables((String) propValue, varResolver));
if (propValue instanceof String strValue) {
entry.setValue(GeneralUtils.replaceVariables(strValue, varResolver));
} else if (propValue instanceof Map) {
patchConfigurationWithProperties((Map<String, Object>) propValue, varResolver);
} else if (propValue instanceof List) {
List value = (List) propValue;
} else if (propValue instanceof List value) {
for (int i = 0; i < value.size(); i++) {
Object colItem = value.get(i);
if (colItem instanceof String) {
value.set(i, GeneralUtils.replaceVariables((String) colItem, varResolver));
if (colItem instanceof String strItem) {
value.set(i, GeneralUtils.replaceVariables(strItem, varResolver));
} else if (colItem instanceof Map) {
patchConfigurationWithProperties((Map<String, Object>) colItem, varResolver);
}
@@ -218,6 +211,8 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
@Override
public Object start(IApplicationContext context) {
initializeApplicationServices();
setWorkspacePath(getServerConfigurationController().getWorkspacePath());
try {
startServer();
} catch (Exception e) {
@@ -255,27 +250,17 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
@NotNull
@Override
public Path getWorkspaceDirectory() {
return getServerConfigurationController().getWorkspacePath();
}
public String getApplicationId() {
try {
return getApplicationInstanceId();
} catch (DBException e) {
return null;
// Fallback to hash code
return getClass().getSimpleName() + "#" + hashCode();
}
}
@NotNull
@Override
public WSEventController getEventController() {
return null;
}
public abstract ServletServerConfigurationController getServerConfigurationController();
public abstract ServletServerConfigurationController<?> getServerConfigurationController();
@Override
public boolean isEnvironmentVariablesAccessible() {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -53,9 +53,6 @@ public interface ServletApplication extends DBPApplication {
@NotNull
Path getDataDirectory(boolean create);
@NotNull
Path getWorkspaceDirectory();
@NotNull
Path getHomeDirectory();
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -23,6 +23,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
public interface ServletAuthApplication extends ServletApplication {
@NotNull
ServletAuthConfiguration getAuthConfiguration();
@Nullable
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -67,8 +67,7 @@ public abstract class BaseLocalResourceController implements RMController {
@Nullable
@Override
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties)
throws DBException {
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties) throws DBException {
RMProject project = makeProjectFromId(projectId, true);
if (project == null) {
return null;
@@ -110,8 +109,7 @@ public abstract class BaseLocalResourceController implements RMController {
}
@Override
public String getProjectsDataSources(@NotNull String projectId, @Nullable String[] dataSourceIds)
throws DBException {
public String getProjectsDataSources(@NotNull String projectId, @Nullable String[] dataSourceIds) throws DBException {
DBPProject projectMetadata = getWebProject(projectId, false);
return doFileReadOperation(
projectId,
@@ -40,6 +40,7 @@ import org.jkiss.dbeaver.model.fs.lock.LockTarget;
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.net.DBWNetworkProfile;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
@@ -48,6 +49,7 @@ 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.model.websocket.event.datasource.WSNetworkProfileEvent;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
@@ -407,8 +409,13 @@ public class LocalResourceController extends BaseLocalResourceController {
registry::createDataSource
)
);
Map<String, DBWNetworkProfile> oldNetworkProfiles = registry.getNetworkProfiles().getProfiles().stream()
.collect(Collectors.toMap(
DBWNetworkProfile::getProfileId,
p -> p
));
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
sendConfigUpdatedEvent(registry, oldDataSources, oldNetworkProfiles, parseResults);
return parseResults != null;
}
@@ -501,14 +508,48 @@ public class LocalResourceController extends BaseLocalResourceController {
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
}
private void sendDataSourcesConfigUpdatedEvent(
private void sendConfigUpdatedEvent(
@NotNull DBPDataSourceRegistry registry,
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
@NotNull Map<String, DBWNetworkProfile> oldNetworkProfiles,
@Nullable DataSourceParseResults parseResults
) {
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null) {
return;
}
boolean profilesChanged = !parseResults.removedProfiles.isEmpty();
// We don't need to check for updated profiles if there are already added or removed profiles
if (!profilesChanged) {
for (DBWNetworkProfile updatedProfile : parseResults.updatedProfiles) {
if (oldNetworkProfiles.containsKey(updatedProfile.getProfileId())) {
DBWNetworkProfile oldProfile = oldNetworkProfiles.get(updatedProfile.getProfileId());
if (!oldProfile.equalConfigurations(updatedProfile)) {
profilesChanged = true;
break;
}
} else {
// profile was added
profilesChanged = true;
break;
}
}
}
if (profilesChanged) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSNetworkProfileEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId()
)
);
}
if (oldDataSources.isEmpty()) {
return;
}
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
List<String> updatedNameDataSourceIds = new ArrayList<>();
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
@@ -612,7 +653,6 @@ public class LocalResourceController extends BaseLocalResourceController {
)
);
}
}
@NotNull
@@ -123,6 +123,16 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
}
public void migrateEventHandlersTo(@NotNull BaseWebSession target) {
synchronized (sessionEventHandlers) {
for (CBWebSessionEventHandler handler : sessionEventHandlers) {
handler.migrateToSession(target);
target.addEventHandler(handler);
}
sessionEventHandlers.clear();
}
}
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
return userContext.refresh(smAuthInfo);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -68,6 +68,13 @@ public class WebSessionEventsFilter {
}
}
public void migrateTo(@NotNull WebSessionEventsFilter target) {
synchronized (target.subscribedProjectIds) {
target.subscribedEventTopics.addAll(this.subscribedEventTopics);
target.subscribedProjectIds.addAll(this.subscribedProjectIds);
}
}
public boolean isEventAllowed(WSEvent event) {
if (event.isForceProcessed()) {
return true;
@@ -54,7 +54,7 @@ public class ServerGlobalWorkspace extends BaseWorkspaceImpl {
@NotNull DBPPlatform platform,
@NotNull ServletApplication application
) {
super(platform, application.getWorkspaceDirectory());
super(platform, application.getWorkspacePath());
this.application = application;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -30,7 +30,7 @@ public interface DBWServiceBindingServlet<APPLICATION extends ServletApplication
return true;
}
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
void addServlets(@NotNull APPLICATION application, @NotNull DBWServletContext servletContext) throws DBException;
@NotNull
default List<String> getExcludedServletPaths(@NotNull APPLICATION application) {
@@ -303,6 +303,10 @@ public class ServletAppUtils {
if (log.isTraceEnabled()) {
log.trace("Origin header: " + origin);
}
if ("null".equals(origin)) {
//strange identity provider bug
origin = null;
}
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
if (log.isTraceEnabled()) {
@@ -33,6 +33,7 @@ import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
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.DBPConnectionType;
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
@@ -40,6 +41,7 @@ import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -320,6 +322,13 @@ public class WebDataSourceUtils {
if (config.getConfigurationType() != null) {
dsConfig.setConfigurationType(config.getConfigurationType());
}
if (config.getConnectionType() != null) {
DBPConnectionType connectionType = DataSourceProviderRegistry.getInstance().getConnectionType(config.getConnectionType(), null);
if (connectionType != null) {
dsConfig.setConnectionType(connectionType);
}
}
if (CommonUtils.isEmpty(config.getUrl())) {
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -16,6 +16,8 @@
*/
package io.cloudbeaver.websocket;
import io.cloudbeaver.model.session.BaseWebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
@@ -24,4 +26,6 @@ public interface CBWebSessionEventHandler {
void close();
void migrateToSession(@NotNull BaseWebSession newSession);
}
@@ -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: 26.1.2.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
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>26.1.2-SNAPSHOT</version>
<version>26.1.4-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.149.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260803
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.149-SNAPSHOT</version>
<version>1.0.151-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver Community
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 26.1.2.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
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>26.1.2-SNAPSHOT</version>
<version>26.1.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -14,11 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.admin;
package io.cloudbeaver.model.config;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
@@ -57,7 +57,7 @@ public class AdminServerConfig {
@Nullable
private String bindSessionToIp;
public AdminServerConfig(Map<String, Object> params) {
public AdminServerConfig(@NotNull Map<String, Object> params) {
this.serverName = JSONUtils.getString(params, "serverName");
this.adminName = JSONUtils.getString(params, "adminName");
this.adminPassword = JSONUtils.getString(params, "adminPassword");
@@ -181,6 +181,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return getServerConfigurationController().getServerConfiguration();
}
@NotNull
@Override
public ServletAuthConfiguration getAuthConfiguration() {
return getAppConfiguration();
@@ -228,11 +229,11 @@ public abstract class CBApplication<T extends CBServerConfig>
Location instanceLoc = Platform.getInstanceLocation();
try {
if (!instanceLoc.isSet()) { // always false?
URL wsLocationURL = getWorkspaceDirectory().toUri().toURL();
URL wsLocationURL = getWorkspacePath().toUri().toURL();
instanceLoc.set(wsLocationURL, true);
}
} catch (Exception e) {
log.error("Error setting workspace location to " + getWorkspaceDirectory().toAbsolutePath(), e);
log.error("Error setting workspace location to " + getWorkspacePath().toAbsolutePath(), e);
return;
}
this.systemInformationCollector = createSystemInformationCollector();
@@ -443,7 +444,7 @@ public abstract class CBApplication<T extends CBServerConfig>
@NotNull
public Path getDataDirectory(boolean create) {
Path dataDir = getWorkspaceDirectory().resolve(CBConstants.RUNTIME_DATA_DIR_NAME);
Path dataDir = getWorkspacePath().resolve(CBConstants.RUNTIME_DATA_DIR_NAME);
if (create && !Files.exists(dataDir)) {
try {
Files.createDirectories(dataDir);
@@ -751,6 +752,7 @@ public abstract class CBApplication<T extends CBServerConfig>
eventController.addEvent(new WSServerConfigurationChangedEvent(sessionId, null));
}
@NotNull
@Override
public abstract CBServerConfigurationController<T> getServerConfigurationController();
@@ -93,6 +93,7 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
return new LocalFileController(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath().resolve(DBFileController.DATA_FOLDER));
}
@NotNull
@Override
public CBServerConfigurationControllerEmbedded<CBServerConfig> getServerConfigurationController() {
return serverConfigController;
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -20,10 +20,7 @@ import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import io.cloudbeaver.model.app.BaseServerConfigurationController;
import io.cloudbeaver.model.app.BaseServletApplication;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.config.PasswordPolicyConfiguration;
import io.cloudbeaver.model.config.SMControllerConfiguration;
import io.cloudbeaver.model.config.*;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -374,6 +371,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
try (Writer out = new OutputStreamWriter(Files.newOutputStream(runtimeConfigPath), StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder()
.setStrictness(Strictness.LENIENT)
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.setPrettyPrinting()
.create();
gson.toJson(configurationProperties, out);
@@ -508,7 +506,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
@NotNull
protected Map<String, Object> collectServerConfigProperties(
@NotNull CBServerConfig serverConfig,
Map<String, Object> originServerConfig
@NotNull Map<String, Object> originServerConfig
) {
var serverConfigProperties = new LinkedHashMap<String, Object>();
if (!CommonUtils.isEmpty(serverConfig.getServerName())) {
@@ -633,6 +631,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
return serverConfiguration;
}
@NotNull
public abstract CBServerConfigurationMapper<T, ? extends AdminServerConfig> createServerConfigurationInputMapper();
public CBAppConfig getAppConfiguration() {
return appConfiguration;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -19,6 +19,7 @@ package io.cloudbeaver.server;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import io.cloudbeaver.model.config.AdminServerConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.config.WebDatabaseConfig;
import org.jkiss.code.NotNull;
@@ -41,6 +42,11 @@ public class CBServerConfigurationControllerEmbedded<T extends CBServerConfig> e
super(serverConfig, homeDirectory);
}
@NotNull
public CBServerConfigurationMapper<T, ? extends AdminServerConfig> createServerConfigurationInputMapper() {
return new CBServerConfigurationMapper<>();
}
@NotNull
@Override
protected Map<String, Object> collectServerConfigProperties(
@@ -0,0 +1,145 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import io.cloudbeaver.DBWFeatureSet;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.config.AdminServerConfig;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.utils.CommonUtils;
import java.util.*;
public class CBServerConfigurationMapper<C extends CBServerConfig, I extends AdminServerConfig> {
private static final Log log = Log.getLog(CBServerConfigurationMapper.class);
@NotNull
public CBServerConfigurations loadServerConfigurationsFromParams(@NotNull Map<String, Object> params) {
CBAppConfig appConfig = new CBAppConfig(CBApplication.getInstance().getAppConfiguration());
C serverConfig = createServerConfiguration();
serverConfig.setServerName(CBApplication.getInstance().getServerName());
serverConfig.setMaxSessionIdleTime(CBApplication.getInstance().getMaxSessionIdleTime());
String adminName = null;
String adminPassword = null;
if (!params.isEmpty()) { // FE can send an empty configuration
I config = createInputConfiguration(params);
adminName = config.getAdminName();
adminPassword = config.getAdminPassword();
populateConfigurations(config, serverConfig, appConfig);
}
return new CBServerConfigurations(
serverConfig,
appConfig,
adminName,
adminPassword
);
}
protected void populateConfigurations(@NotNull I input, @NotNull C serverConfig, @NotNull CBAppConfig appConfig) {
appConfig.setAnonymousAccessEnabled(input.isAnonymousAccessEnabled());
appConfig.setSupportsCustomConnections(input.isCustomConnectionsEnabled());
appConfig.setPublicCredentialsSaveEnabled(input.isPublicCredentialsSaveEnabled());
appConfig.setAdminCredentialsSaveEnabled(input.isAdminCredentialsSaveEnabled());
updateDisabledFeaturesConfig(appConfig, input.getEnabledFeatures());
// custom logic for enabling embedded drivers
updateDisabledDriversConfig(appConfig, input.getDisabledDrivers());
appConfig.setResourceManagerEnabled(input.isResourceManagerEnabled());
appConfig.setSecretManagerEnabled(input.isSecretManagerEnabled());
if (CommonUtils.isEmpty(input.getEnabledAuthProviders())) {
// All of them
appConfig.setEnabledAuthProviders(new String[0]);
} else {
appConfig.setEnabledAuthProviders(input.getEnabledAuthProviders().toArray(new String[0]));
}
appConfig.setDefaultNavigatorSettings(
CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings());
serverConfig.setServerName(input.getServerName());
serverConfig.setMaxSessionIdleTime(input.getSessionExpireTime());
if (input.getForceHttps() != null) {
serverConfig.setForceHttps(input.getForceHttps());
}
if (input.getSupportedHosts() != null) {
serverConfig.setSupportedHosts(input.getSupportedHosts());
}
if (input.getBindSessionToIp() != null) {
serverConfig.setBindSessionToIp(input.getBindSessionToIp());
}
}
private void updateDisabledFeaturesConfig(@NotNull CBAppConfig appConfig, @NotNull List<String> enabledFeatures) {
Set<String> enabledIds = new LinkedHashSet<>(enabledFeatures);
appConfig.setEnabledFeatures(enabledFeatures.toArray(new String[0]));
String[] disabledFeatures = ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures().stream()
.map(DBWFeatureSet::getId)
.filter(id -> !enabledIds.contains(id))
.toArray(String[]::new);
appConfig.setDisabledFeatures(disabledFeatures);
}
// we disable embedded drivers by default and enable it in enabled drivers list
// that's why we need so complicated logic for disabling drivers
private void updateDisabledDriversConfig(@NotNull CBAppConfig appConfig, @NotNull String[] disabledDriversConfig) {
Set<String> disabledIds = new LinkedHashSet<>(Arrays.asList(disabledDriversConfig));
Set<String> enabledIds = new LinkedHashSet<>(Arrays.asList(appConfig.getEnabledDrivers()));
// remove all disabled embedded drivers from enabled drivers list
enabledIds.removeAll(disabledIds);
// enable embedded driver if it is not in disabled drivers list
for (String driverId : appConfig.getDisabledDrivers()) {
if (disabledIds.contains(driverId)) {
// driver is also disabled
continue;
}
// driver is removed from disabled list
// we need to enable if it is embedded
try {
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
if (driver.isEmbedded()) {
enabledIds.add(driverId);
}
} catch (DBWebException e) {
log.error("Failed to find driver by id", e);
}
}
appConfig.setDisabledDrivers(disabledDriversConfig);
appConfig.setEnabledDrivers(enabledIds.toArray(String[]::new));
}
@NotNull
protected C createServerConfiguration() {
return (C) new CBServerConfig();
}
@NotNull
protected I createInputConfiguration(@NotNull Map<String, Object> params) {
return (I) new AdminServerConfig(params);
}
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
public record CBServerConfigurations(
@NotNull CBServerConfig serverConfig,
@NotNull CBAppConfig appConfig,
@Nullable String adminName,
@Nullable String adminPassword
) {
}
@@ -202,6 +202,43 @@ public class CBSessionManager implements WebAppSessionManager {
return httpSession.getId();
}
/**
* Invalidates the current HTTP session, creates a new one, and binds a new {@link WebSession} to it.
*/
@NotNull
public WebSession rotateSession(
@NotNull HttpServletRequest request,
@NotNull WebSession oldWebSession
) throws DBWebException {
HttpSession oldHttpSession = request.getSession(false);
if (oldHttpSession != null) {
oldHttpSession.invalidate();
}
String newSessionId = request.getSession(true).getId();
String locale = oldWebSession.getLocale();
String remoteAddr = oldWebSession.getLastRemoteAddr();
String remoteUserAgent = oldWebSession.getLastRemoteUserAgent();
var requestInfo = new WebHttpRequestInfo(newSessionId, locale, remoteAddr, remoteUserAgent);
WebSession newWebSession;
try {
newWebSession = createWebSessionImpl(requestInfo);
} catch (DBException e) {
throw new DBWebException(e);
}
oldWebSession.migrateEventHandlersTo(newWebSession);
oldWebSession.getEventsFilter().migrateTo(newWebSession.getEventsFilter());
String oldSessionId = oldWebSession.getSessionId();
synchronized (sessionMap) {
sessionMap.remove(oldSessionId);
sessionMap.put(newSessionId, newWebSession);
}
oldWebSession.close(false, false);
log.debug("Session rotated '" + oldSessionId + "' -> '" + newSessionId + "'");
return newWebSession;
}
/**
* Returns not expired session from cache, or restore it.
*
@@ -288,13 +325,12 @@ public class CBSessionManager implements WebAppSessionManager {
@Nullable
public WebSession findWebSession(@NotNull HttpServletRequest request) {
String sessionId = getSessionId(request);
WebSession webSession;
synchronized (sessionMap) {
var session = sessionMap.get(sessionId);
if (session instanceof WebSession) {
return (WebSession) session;
}
return null;
webSession = (session instanceof WebSession) ? (WebSession) session : null;
}
return webSession;
}
@Nullable
@@ -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: 26.1.2.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
@@ -45,6 +45,9 @@
<eventHandler class="io.cloudbeaver.server.events.WSDataSourceUpdatedEventHandlerImpl">
<topic id="cb_datasource"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSNetworkProfileEventHandler">
<topic id="cb_profiles"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSFolderUpdatedEventHandlerImpl">
<topic id="cb_datasource_folder"/>
</eventHandler>
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>26.1.2-SNAPSHOT</version>
<version>26.1.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -570,6 +570,8 @@ type ConnectionInfo {
"List of tools that can be used with this connection. Returns empty list if no tools are available"
tools: [String!]! @since(version: "24.1.3")
connectionType: ID! @since(version: "26.1.2")
}
type ConnectionFolderInfo {
@@ -633,6 +635,39 @@ type LogEntry {
stackTrace: String
}
type ConnectionType @since(version: "26.1.2") {
"Unique identifier of the connection type"
id: ID!
"Human-readable name of the connection type"
name: String!
"Description of the connection type"
description: String
"Color of the connection type (for light theme)"
colorLight: String!
"Alternative color of the connection type (for dark theme)"
colorDark: String
"Is autocommit enabled for this connection type"
autocommit: Boolean!
"Confirm execution of queries for this connection type"
confirmExecute: Boolean!
"Confirm data change operations (INSERT, UPDATE, DELETE) for this connection type"
confirmDataChange: Boolean!
"Smart commit mode is enabled for this connection type"
smartCommit: Boolean!
"Recover smart commit mode is enabled for this connection type"
smartCommitRecover: Boolean!
"Auto-close transactions is enabled for this connection type"
autoCloseTransactions: Boolean!
"Auto-close idle transactions period in seconds for this connection type"
closeIdleTransactionPeriod: Int!
"Auto-close connections is enabled for this connection type"
autoCloseConnections: Boolean!
"Auto-close idle connections period in seconds for this connection type"
closeIdleConnectionPeriod: Int!
"Specifies whether this connection type is system-defined or user-defined. System-defined connection types cannot be deleted."
predefined: Boolean!
}
####################################################
# Input types
####################################################
@@ -745,6 +780,8 @@ input ConnectionConfig {
defaultSchemaName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Default user settings for connection"
defaultUserPreferences: Object @since(version: "26.0.2")
connectionType: ID @since(version: "26.1.2")
}
####################################################
@@ -790,6 +827,8 @@ extend type Query {
"Returns connection driver properties based on configuration"
listConnectionDriverProperties( projectId: ID!, config: ConnectionConfig! ): [ObjectPropertyInfo!]!
listConnectionTypes: [ConnectionType!]! @since(version: "26.1.2")
}
extend type Mutation {
@@ -68,19 +68,39 @@ input SQLDataFilter {
limit: Int
constraints: [ SQLDataFilterConstraint ]
"When true constraints are combined with OR instead of AND"
anyConstraint: Boolean
where: String
orderBy: String
}
type SQLResultReference @since(version: "26.1.1") {
"Pair (source-row column index, target-entity column name) used to build a navigation filter"
type SQLReferenceColumnMapping @since(version: "26.1.2") {
"Position in the result-set bindings"
sourceColumnIndex: Int!
"Column name in the source result set"
sourceColumnName: String!
"Ordinal position of the column in the target entity definition"
targetColumnIndex: Int!
"Column name in the target entity"
targetColumnName: String!
}
type SQLResultAssociation @since(version: "26.1.2") {
"True for reverse references (incoming FKs)"
reference: Boolean!
"Name of the association (foreign key)"
associationName: String!
"Fully qualified name of the entity"
"Name of the catalog the target entity belongs to"
targetCatalogName: String
"Name of the schema the target entity belongs to"
targetSchemaName: String
"Plain name of the target entity"
targetEntityName: String
"Navigator node URI of the target entity, suitable for opening it directly in the UI. Null if the entity is not present in the navigator tree"
nodePath: String
"Indices of result-set columns that participate as the source-side attributes of this reference"
columnIndexList: [Int!]!
targetNodePath: String
"List of column mappings between the source result set and the target entity"
columnMapping: [SQLReferenceColumnMapping!]!
}
type SQLResultColumn {
@@ -133,11 +153,6 @@ type SQLResultSet {
"Returns list of rows in the result set. Each row contains data and metadata"
rowsWithMetaData: [SQLResultRowMetaData] @since(version: "23.3.5")
"Forward foreign keys navigable from this result set"
associations: [SQLResultReference!]! @since(version: "26.1.1")
"Reverse references (incoming FKs from other entities) navigable from this result set"
references: [SQLResultReference!]! @since(version: "26.1.1")
# True means that resultset was generated by single entity query
# New rows can be added, old rows can be deleted
singleEntity: Boolean!
@@ -328,6 +343,15 @@ type TransactionLogInfos {
extend type Query {
"Foreign-key navigation metadata for the given result set. Set isReference to true for reverse references, false for forward associations, or omit it to get both"
sqlResultAssociations(
projectId: ID,
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
isReference: Boolean
): [SQLResultAssociation!]! @since(version: "26.1.2")
"Returns SQL dialect info for the specified connection"
sqlDialectInfo( projectId: ID, connectionId: ID! ): SQLDialectInfo
@@ -467,19 +491,6 @@ extend type Mutation {
dataFormat: ResultDataFormat
): AsyncTaskInfo!
"Creates async task for navigating a foreign-key reference exposed in SQLResultSet.references. Pass associationName, isReference and any one of the chosen reference's columnIndices (used to locate the source entity)."
asyncSqlNavigateForeignKey(
projectId: ID,
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
row: SQLResultRow!,
columnIndex: Int!,
associationName: String!,
isReference: Boolean!,
dataFormat: ResultDataFormat
): AsyncTaskInfo! @since(version: "26.1.1")
"Returns transaction log info for the specified project, connection and context"
getTransactionLogInfo(
projectId: ID!,
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -19,6 +19,7 @@ package io.cloudbeaver.server.events;
import io.cloudbeaver.server.BaseWebPlatform;
import io.cloudbeaver.server.WebAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.websocket.WSEventHandler;
@@ -33,7 +34,10 @@ public class WSDeleteTempFileHandler implements WSEventHandler<WSEventDeleteTemp
private static final Log log = Log.getLog(WSDeleteTempFileHandler.class);
public void resetTempFolder(String sessionId) {
public void resetTempFolder(@Nullable String sessionId) {
if (sessionId == null) {
return;
}
Path path = WebAppUtils.getWebPlatform()
.getTempFolder(new VoidProgressMonitor(), BaseWebPlatform.TEMP_FILE_FOLDER)
.resolve(sessionId);
@@ -0,0 +1,56 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 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.events;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSNetworkProfileEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
public class WSNetworkProfileEventHandler extends WSDefaultEventHandler<WSNetworkProfileEvent> {
private static final Log log = Log.getLog(WSNetworkProfileEventHandler.class);
@Override
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSNetworkProfileEvent event) {
if (event.getProjectId() != null) {
if (activeUserSession instanceof WebSession webSession) {
WebSessionProjectImpl project = webSession.getProjectById(event.getProjectId());
if (project == null) {
log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId());
return;
}
project.getDataSourceRegistry().getNetworkProfiles().reloadProfiles();
} else {
super.updateSessionData(activeUserSession, event);
}
} else {
DBWorkbench.getPlatform().getNetworkProfiles().reloadProfiles();
super.updateSessionData(activeUserSession, event);
}
}
@Override
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSNetworkProfileEvent event) {
if (event.getProjectId() != null && !activeUserSession.isProjectAccessible(event.getProjectId())) {
return false;
}
return super.isAcceptableInSession(activeUserSession, event);
}
}
@@ -33,12 +33,16 @@ public class CBClientEventProcessor {
private static final Log log = Log.getLog(CBClientEventProcessor.class);
final BaseWebSession webSession;
volatile BaseWebSession webSession;
public CBClientEventProcessor(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}
void setWebSession(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}
public void process(@Nullable String message) {
if (CommonUtils.isEmpty(message)) {
return;
@@ -40,7 +40,7 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
private static final int QUEUE_CAPACITY = 1000;
private final BaseWebSession webSession;
private volatile BaseWebSession webSession;
private final BlockingQueue<WSEvent> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
private final CBClientEventProcessor processor;
private volatile long lastPoll;
@@ -88,6 +88,12 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
return result;
}
@Override
public void migrateToSession(@NotNull BaseWebSession newSession) {
this.webSession = newSession;
this.processor.setWebSession(newSession);
}
@Override
public void handleWebSessionEvent(@NotNull WSEvent event) {
if (!queue.offer(event)) {
@@ -32,7 +32,11 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
private static final Log log = Log.getLog(CBEventsWebSocket.class);
@Nullable
private BaseWebSession webSession;
private volatile BaseWebSession webSession;
@Nullable
private FromUserEventHandler eventProcessor;
@Nullable
private WebSocketPingPongCallback pingPongCallback;
@Override
public void onOpen(Session session, EndpointConfig config) {
@@ -48,13 +52,31 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
session.setMaxIdleTimeout(Duration.ofMinutes(5).toMillis());
session.addMessageHandler(String.class, new FromUserEventHandler(webSession));
session.addMessageHandler(PongMessage.class, new WebSocketPingPongCallback(webSession));
this.eventProcessor = new FromUserEventHandler(webSession);
session.addMessageHandler(String.class, eventProcessor);
this.pingPongCallback = new WebSocketPingPongCallback(webSession);
session.addMessageHandler(PongMessage.class, pingPongCallback);
CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this);
}
}
@Override
public void migrateToSession(@NotNull BaseWebSession newSession) {
BaseWebSession oldSession = this.webSession;
this.webSession = newSession;
if (eventProcessor != null) {
eventProcessor.setWebSession(newSession);
}
if (pingPongCallback != null) {
pingPongCallback.setWebSession(newSession);
}
if (oldSession != null) {
CBJettyWebSocketManager.migrateWebSocket(oldSession.getSessionId(), newSession.getSessionId(), this);
}
log.debug("EventWebSocket migrated to the " + newSession.getSessionId() + " session");
}
private static class FromUserEventHandler extends CBClientEventProcessor implements MessageHandler.Whole<String> {
private FromUserEventHandler(@NotNull BaseWebSession webSession) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -39,6 +39,25 @@ public class CBJettyWebSocketManager {
socketBySessionId.computeIfAbsent(webSessionId, key -> new CopyOnWriteArrayList<>()).add(webSocket);
}
/**
* Re-keys a live web socket from the old session id to the new one after a session rotation,
* so that keep-alive pings keep being delivered to it.
*/
public static void migrateWebSocket(
@NotNull String oldSessionId,
@NotNull String newSessionId,
@NotNull CBEventsWebSocket webSocket
) {
List<CBEventsWebSocket> oldSockets = socketBySessionId.get(oldSessionId);
if (oldSockets != null) {
oldSockets.remove(webSocket);
if (oldSockets.isEmpty()) {
socketBySessionId.remove(oldSessionId);
}
}
registerWebSocket(newSessionId, webSocket);
}
public static void sendPing() {
//remove expired sessions
WebAppSessionManager webSessionManager = WebAppUtils.getWebApplication().getSessionManager();
@@ -24,12 +24,16 @@ import org.jkiss.code.NotNull;
public class WebSocketPingPongCallback implements MessageHandler.Whole<PongMessage> {
@NotNull
private final BaseWebSession webSession;
private volatile BaseWebSession webSession;
public WebSocketPingPongCallback(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}
void setWebSession(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}
@Override
public void onMessage(PongMessage message) {
if (webSession instanceof WebHeadlessSession) {
@@ -275,6 +275,12 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
if (project == null) {
throw new DBException("Project not found:" + projectId);
}
var customConnectionsEnabled =
ServletAppUtils.getServletApplication().getAppConfiguration().isSupportsCustomConnections();
if (!customConnectionsEnabled && project.isPrivateProject()) {
throw new DBWebExceptionAccessDenied("Access to private project is denied");
}
RMProject rmProject = project.getRMProject();
for (String reqProjectPermission : requireProjectPermissions) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -71,7 +71,7 @@ public abstract class WebServiceServletBase extends HttpServlet {
} catch (Exception e) {
log.error(e);
errorMessage = e.getMessage();
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + errorMessage);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + flattenForResponse(errorMessage));
} finally {
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
apiCallInterceptor.onApiCallEvent(
@@ -83,6 +83,17 @@ public abstract class WebServiceServletBase extends HttpServlet {
protected abstract void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException;
/**
* Replaces control characters (line breaks, tabs, etc.) with spaces so the message can be safely
* embedded into the JSON error body produced by the servlet container's error handler.
*/
private String flattenForResponse(String message) {
if (message == null) {
return null;
}
return message.replaceAll("\\p{Cntrl}+", " ").trim();
}
protected Map<String, Object> getVariables(HttpServletRequest request) {
return gson.fromJson(request.getParameter(REQUEST_PARAM_VARIABLES), MAP_STRING_OBJECT_TYPE);
}
@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.connection.DBPConnectionType;
import org.jkiss.dbeaver.model.rm.RMConstants;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
@@ -115,6 +116,9 @@ public interface DBWServiceCore extends DBWService {
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@NotNull
List<DBPConnectionType> getConnectionTypes(@NotNull WebSession webSession, @Nullable String id);
@WebAction
WebConnectionInfo getConnectionState(WebSession webSession, @Nullable String projectId, String connectionId) throws DBWebException;
@@ -159,17 +163,17 @@ public interface DBWServiceCore extends DBWService {
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@WebAction
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
WebConnectionInfo testConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@Nullable @WebObjectId String projectId,
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@WebAction
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
WebNetworkEndpointInfo testNetworkHandler(
@NotNull WebSession webSession,
@Nullable String projectId,
@Nullable @WebObjectId String projectId,
@Nullable String connectionId,
@NotNull WebNetworkHandlerConfigInput nhConfig
) throws DBWebException;
@@ -87,6 +87,11 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
getArgumentVal(env, "projectId"),
getArgumentVal(env, "config")
)
).dataFetcher(
"listConnectionTypes", env -> getService(env).getConnectionTypes(
getWebSession(env),
getArgument(env, "id")
)
)
;
@@ -45,6 +45,7 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.auth.SMObjectType;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPConnectionType;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.exec.DBCConnectException;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
@@ -310,17 +311,32 @@ public class WebServiceCore implements DBWServiceCore {
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, configInput.getConnectionId());
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
DBPConnectionConfiguration connectionConfiguration = new DBPConnectionConfiguration(testDataSource.getConnectionConfiguration());
return WebServiceUtils.getDriverProperties(
webSession,
testDataSource.getDriver(),
testDataSource,
connectionConfiguration
);
try {
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
DBPConnectionConfiguration connectionConfiguration
= new DBPConnectionConfiguration(testDataSource.getConnectionConfiguration());
return WebServiceUtils.getDriverProperties(
webSession,
testDataSource.getDriver(),
testDataSource,
connectionConfiguration
);
} catch (DBWebException e) {
log.error("Error getting driver properties", e);
return new WebPropertyInfo[0];
}
}
@NotNull
@Override
public List<DBPConnectionType> getConnectionTypes(@NotNull WebSession webSession, @Nullable String id) {
return DataSourceProviderRegistry.getInstance().getConnectionTypes().stream()
.map(ct -> id == null || id.equals(ct.getId()) ? ct : null)
.filter(Objects::nonNull)
.toList();
}
@Override
public WebConnectionInfo initConnection(
@@ -541,6 +557,9 @@ public class WebServiceCore implements DBWServiceCore {
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, configInput.getConnectionId());
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
testDataSource.setTemporary(true);
WebConnectionInfo connectionInfo = project.addConnection(testDataSource);
connectionInfo.setSavedCredentials(configInput.getCredentials(), configInput.getNetworkHandlersConfig());
try {
ConnectionTestJob ct = new ConnectionTestJob(
testDataSource, param -> {
@@ -557,7 +576,6 @@ public class WebServiceCore implements DBWServiceCore {
}
throw new DBWebException("Connection failed", ct.getConnectError());
}
WebConnectionInfo connectionInfo = project.createConnectionInfo(testDataSource);
connectionInfo.setConnectError(ct.getConnectError());
connectionInfo.setServerVersion(ct.getServerVersion());
connectionInfo.setClientVersion(ct.getClientVersion());
@@ -565,6 +583,8 @@ public class WebServiceCore implements DBWServiceCore {
return connectionInfo;
} catch (DBException e) {
throw new DBWebException("Error connecting to database", e);
} finally {
project.removeConnection(testDataSource);
}
}
@@ -585,11 +605,7 @@ public class WebServiceCore implements DBWServiceCore {
}
testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry());
WebDataSourceUtils.setConnectionConfiguration(
testDataSource.getDriver(),
testDataSource.getConnectionConfiguration(),
configInput
);
project.updateDataSourceContainerFromInput(configInput, testDataSource);
if (configInput.getSelectedSecretId() != null) {
try {
dataSource.listSharedCredentials()
@@ -279,7 +279,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
// in that way all related scripts will use the same context and overwrite the user's define context
// So why we need restore the default execution context after refreshing datasource
DBCExecutionContext defaultContext = DBUtils.getDefaultContext(refreshableObject, false);
DBCExecutionContextDefaults contextDefaults = defaultContext.getContextDefaults();
DBCExecutionContextDefaults<?, ?> contextDefaults = defaultContext.getContextDefaults();
refreshableObject.refreshObject(monitor);
if (contextDefaults != null && contextDefaults.getDefaultSchema() != null
&& contextDefaults.getDefaultCatalog() != null) {
@@ -603,8 +603,8 @@ public class WebServiceNavigator implements DBWServiceNavigator {
}
private void checkProjectEditAccess(@NotNull DBNNode node, @NotNull WebSession session) throws DBException {
BaseWebProjectImpl project = (BaseWebProjectImpl) node.getOwnerProject();
if (project == null || !hasNodeEditPermission(session, node, project.getRMProject())) {
var project = node.getOwnerProject();
if (!(project instanceof BaseWebProjectImpl bwp) || !hasNodeEditPermission(session, node, bwp.getRMProject())) {
throw new DBException("Access denied");
}
}
@@ -127,15 +127,12 @@ public interface DBWServiceSQL extends DBWService {
@Nullable WebDataFormat dataFormat) throws DBWebException;
@WebAction
WebAsyncTaskInfo asyncNavigateForeignKey(
List<WebSQLQueryResultAssociation> getSqlResultAssociations(
@NotNull WebSession webSession,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
@NotNull WebSQLResultsRow row,
int columnIndex,
@NotNull String associationName,
boolean isReference,
@Nullable WebDataFormat dataFormat) throws DBException;
@Nullable Boolean isReference
) throws DBException;
/**
* Reads dynamic trace from provided database results.
@@ -41,6 +41,7 @@ public class WebSQLDataFilter {
private int offset;
private int limit;
private String where;
private boolean anyConstraint;
private final List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
public WebSQLDataFilter() {
@@ -57,9 +58,10 @@ public class WebSQLDataFilter {
this.limit = MAX_ROWS_NUMBER;
}
this.where = CommonUtils.toString(filterProps.get("where"), null);
this.anyConstraint = CommonUtils.toBoolean(filterProps.get("anyConstraint"));
Object constraints = filterProps.get("constraints");
if (constraints instanceof Collection) {
for (Object constrItem : (Collection<?>) constraints) {
if (constraints instanceof Collection<?> col) {
for (Object constrItem : col) {
if (constrItem instanceof Map) {
this.constraints.add(
new WebSQLDataFilterConstraint((Map<String, Object>) constrItem));
@@ -92,10 +94,18 @@ public class WebSQLDataFilter {
return where;
}
/**
* When true constraints are combined with OR instead of AND
*/
public boolean isAnyConstraint() {
return anyConstraint;
}
@NotNull
public static WebSQLDataFilter from(@NotNull DBDDataFilter filter) {
var webFilter = new WebSQLDataFilter();
webFilter.where = filter.getWhere();
webFilter.anyConstraint = filter.isAnyConstraint();
for (DBDAttributeConstraint constraint : filter.getConstraints()) {
webFilter.constraints.add(WebSQLDataFilterConstraint.from(constraint));
}
@@ -106,6 +116,7 @@ public class WebSQLDataFilter {
public DBDDataFilter makeDataFilter(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
DBDDataFilter dataFilter = new DBDDataFilter();
dataFilter.setWhere(where);
dataFilter.setAnyConstraint(anyConstraint);
if (CommonUtils.isEmpty(constraints)) {
return dataFilter;
}
@@ -139,14 +150,42 @@ public class WebSQLDataFilter {
private void fillEmptyConstrains(@NotNull List<DBDAttributeConstraint> emptyConstraints) throws DBException {
for (WebSQLDataFilterConstraint webConstr : constraints) {
if (webConstr.getAttributePosition() >= emptyConstraints.size()) {
throw new DBException(MessageFormat.format("Incorrect column position ''{0}'' in order clause", webConstr.getAttributePosition()));
DBDAttributeConstraint dbConstr = findConstraintToFill(emptyConstraints, webConstr);
if (dbConstr.hasCondition()) {
// The attribute already has a condition from a previous constraint
dbConstr = new DBDAttributeConstraint(dbConstr);
dbConstr.reset();
emptyConstraints.add(dbConstr);
}
DBDAttributeConstraint dbConstr = emptyConstraints.get(webConstr.getAttributePosition());
fillEmptyConstraint(dbConstr, webConstr);
}
}
@NotNull
private DBDAttributeConstraint findConstraintToFill(
@NotNull List<DBDAttributeConstraint> emptyConstraints,
@NotNull WebSQLDataFilterConstraint webConstr
) throws DBException {
Integer attributePosition = webConstr.getAttributePosition();
if (attributePosition != null) {
if (attributePosition < 0 || attributePosition >= emptyConstraints.size()) {
throw new DBException(MessageFormat.format("Incorrect column position ''{0}'' in order clause", attributePosition));
}
return emptyConstraints.get(attributePosition);
}
// try by attribute name
String attributeName = webConstr.getAttributeName();
if (!CommonUtils.isEmpty(attributeName)) {
for (DBDAttributeConstraint dbConstr : emptyConstraints) {
if (CommonUtils.equalObjects(dbConstr.getAttributeName(), attributeName)) {
return dbConstr;
}
}
throw new DBException(MessageFormat.format("Constraint attribute ''{0}'' not found in result set", attributeName));
}
throw new DBException("Constraint must specify either attributePosition or attributeName");
}
@NotNull
private DBDAttributeConstraint fillEmptyConstraint(@NotNull DBDAttributeConstraint dbConstr,
@NotNull WebSQLDataFilterConstraint webConstr) {
@@ -168,6 +207,7 @@ public class WebSQLDataFilter {
return dbConstr;
}
@NotNull
private List<DBDAttributeConstraint> getDbdConstraints() {
List<DBDAttributeConstraint> dbConstraints = new ArrayList<>();
for (WebSQLDataFilterConstraint constraint : constraints) {
@@ -182,7 +222,10 @@ public class WebSQLDataFilter {
return dbConstraints;
}
private static int getOriginalVisualPosition(WebSQLDataFilterConstraint constraint, String attributeName) {
private static int getOriginalVisualPosition(
@NotNull WebSQLDataFilterConstraint constraint,
@NotNull String attributeName
) {
Integer attributePosition = constraint.getAttributePosition();
if (CommonUtils.isEmpty(attributeName)) {
@@ -479,7 +479,7 @@ public class WebSQLProcessor implements WebSessionProvider {
sendTransactionalEvent(contextInfo);
}
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(webSession);
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
updatedResultSet.setResultsInfo(resultsInfo);
updatedResultSet.setColumns(resultsInfo.getAttributes());
@@ -651,7 +651,7 @@ public class WebSQLProcessor implements WebSessionProvider {
DBDAttributeBinding[] allAttributes = resultsInfo.getAttributes();
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(webSession);
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
updatedResultSet.setResultsInfo(resultsInfo);
updatedResultSet.setColumns(resultsInfo.getAttributes());
@@ -1131,7 +1131,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull WebSQLExecuteInfo executeInfo,
@NotNull DBDDataFilter filter
) throws DBException {
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
if (filter.getConstraintsCount() > 0 || !CommonUtils.isEmpty(filter.getWhere())) {
StringBuilder where = new StringBuilder();
SQLUtils.appendConditionString(
filter,
@@ -1268,10 +1268,23 @@ public class WebSQLProcessor implements WebSessionProvider {
throws DBException {
if (cellRow instanceof Map<?, ?> variables) {
if (variables.get(FILE_ID) != null) {
Path path = WebAppUtils.getWebPlatform()
String fileId = variables.get(FILE_ID).toString();
try {
// file id must be UUID
UUID.fromString(fileId);
} catch (IllegalArgumentException e) {
throw new DBException("File ID is invalid");
}
Path uploadFolder = WebAppUtils.getWebPlatform()
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
.resolve(webSession.getSessionId())
.resolve(variables.get(FILE_ID).toString());
.normalize();
Path path = uploadFolder.resolve(fileId).normalize();
// ensure the resolved path stays within the session upload directory
if (!path.startsWith(uploadFolder)) {
throw new DBException("File ID is invalid");
}
try {
var file = Files.newInputStream(path);
@@ -54,7 +54,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
private final WebDataFormat dataFormat;
@Nullable
private final DBDDataFilter dataFilter;
private final WebSQLQueryResultSet webResultSet;
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
private DBDAttributeBinding[] bindings;
private DBCTrace trace;
@@ -71,7 +71,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
this.dataContainer = dataContainer;
this.dataFormat = dataFormat;
this.dataFilter = dataFilter;
this.webResultSet = new WebSQLQueryResultSet(contextInfo.getProcessor().getWebSession());
rowLimit = ServletAppUtils.getServletApplication()
.getAppConfiguration()
.getResourceQuota(WebSQLConstants.QUOTA_PROP_ROW_LIMIT);
@@ -21,19 +21,20 @@ 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.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraint;
import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog;
import org.jkiss.dbeaver.model.struct.rdb.DBSSchema;
import java.util.List;
public class WebSQLQueryResultReference {
public class WebSQLQueryResultAssociation {
private static final Log log = Log.getLog(WebSQLQueryResultReference.class);
private static final Log log = Log.getLog(WebSQLQueryResultAssociation.class);
@Nullable
private final WebSession session;
@@ -41,18 +42,23 @@ public class WebSQLQueryResultReference {
private final DBSEntityAssociation association;
private final boolean reverse;
@NotNull
private final List<Integer> columnIndexList;
private final List<WebSQLReferenceColumnMapping> columnMapping;
public WebSQLQueryResultReference(
public WebSQLQueryResultAssociation(
@Nullable WebSession session,
@NotNull DBSEntityAssociation association,
boolean reverse,
@NotNull List<Integer> columnIndexList
@NotNull List<WebSQLReferenceColumnMapping> columnMapping
) {
this.session = session;
this.association = association;
this.reverse = reverse;
this.columnIndexList = columnIndexList;
this.columnMapping = columnMapping;
}
@Property
public boolean isReference() {
return reverse;
}
@NotNull
@@ -63,18 +69,36 @@ public class WebSQLQueryResultReference {
@Nullable
@Property
public String getTargetEntityName() {
public String getTargetCatalogName() {
DBSEntity targetEntity = getTargetEntity();
if (targetEntity == null) {
return null;
}
return DBUtils.getObjectFullName(targetEntity, DBPEvaluationContext.UI);
DBSCatalog catalog = DBUtils.getParentOfType(DBSCatalog.class, targetEntity);
return catalog == null ? null : catalog.getName();
}
@Nullable
@Property
public String getNodePath() {
public String getTargetSchemaName() {
DBSEntity targetEntity = getTargetEntity();
if (targetEntity == null) {
return null;
}
DBSSchema schema = DBUtils.getParentOfType(DBSSchema.class, targetEntity);
return schema == null ? null : schema.getName();
}
@Nullable
@Property
public String getTargetEntityName() {
DBSEntity targetEntity = getTargetEntity();
return targetEntity == null ? null : targetEntity.getName();
}
@Nullable
@Property
public String getTargetNodePath() {
if (session == null) {
return null;
}
@@ -94,8 +118,8 @@ public class WebSQLQueryResultReference {
@NotNull
@Property
public List<Integer> getColumnIndexList() {
return columnIndexList;
public List<WebSQLReferenceColumnMapping> getColumnMapping() {
return columnMapping;
}
@Nullable
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service.sql;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.sql.WebSQLResultSetRowIdentifier.WebSQLResultSetRowIdentifierState;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -36,10 +35,6 @@ public class WebSQLQueryResultSet {
private static final Log log = Log.getLog(WebSQLQueryResultSet.class);
@Nullable
private final WebSession session;
@Nullable
private DBDAttributeBinding[] referencesBindings;
private WebSQLQueryResultColumn[] columns;
private List<WebSQLQueryResultSetRow> rows = Collections.emptyList();
private boolean hasMoreData;
@@ -55,8 +50,7 @@ public class WebSQLQueryResultSet {
private boolean readOnly;
private String readOnlyStatus;
public WebSQLQueryResultSet(@Nullable WebSession session) {
this.session = session;
public WebSQLQueryResultSet() {
}
@Property
@@ -79,23 +73,6 @@ public class WebSQLQueryResultSet {
columns[i] = new WebSQLQueryResultColumn(bindings[i]);
}
this.columns = columns;
this.referencesBindings = bindings;
}
@NotNull
@Property
public List<WebSQLQueryResultReference> getAssociations() {
return session != null && referencesBindings != null
? WebSQLUtils.collectAssociations(session, referencesBindings)
: Collections.emptyList();
}
@NotNull
@Property
public List<WebSQLQueryResultReference> getReferences() {
return session != null && referencesBindings != null
? WebSQLUtils.collectReferences(session, referencesBindings)
: Collections.emptyList();
}
@Property
@@ -0,0 +1,64 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.meta.Property;
public class WebSQLReferenceColumnMapping {
private final int sourceColumnIndex;
@NotNull
private final String sourceColumnName;
private final int targetColumnIndex;
@NotNull
private final String targetColumnName;
public WebSQLReferenceColumnMapping(
int sourceColumnIndex,
@NotNull String sourceColumnName,
int targetColumnIndex,
@NotNull String targetColumnName
) {
this.sourceColumnIndex = sourceColumnIndex;
this.sourceColumnName = sourceColumnName;
this.targetColumnIndex = targetColumnIndex;
this.targetColumnName = targetColumnName;
}
@Property
public int getSourceColumnIndex() {
return sourceColumnIndex;
}
@NotNull
@Property
public String getSourceColumnName() {
return sourceColumnName;
}
@Property
public int getTargetColumnIndex() {
return targetColumnIndex;
}
@NotNull
@Property
public String getTargetColumnName() {
return targetColumnName;
}
}
@@ -331,23 +331,23 @@ public class WebSQLUtils {
}
@NotNull
public static List<WebSQLQueryResultReference> collectAssociations(
public static List<WebSQLQueryResultAssociation> collectAssociations(
@NotNull WebSession session,
@NotNull DBDAttributeBinding[] bindings
) {
return collectWebSQLQueryResultReference(session, bindings, false);
return collectWebSQLQueryResultAssociation(session, bindings, false);
}
@NotNull
public static List<WebSQLQueryResultReference> collectReferences(
public static List<WebSQLQueryResultAssociation> collectReferences(
@NotNull WebSession session,
@NotNull DBDAttributeBinding[] bindings
) {
return collectWebSQLQueryResultReference(session, bindings, true);
return collectWebSQLQueryResultAssociation(session, bindings, true);
}
@NotNull
private static List<WebSQLQueryResultReference> collectWebSQLQueryResultReference(
private static List<WebSQLQueryResultAssociation> collectWebSQLQueryResultAssociation(
@NotNull WebSession session,
@NotNull DBDAttributeBinding[] bindings,
boolean reverse
@@ -369,7 +369,7 @@ public class WebSQLUtils {
return idx == null ? null : bindings[idx];
};
List<WebSQLQueryResultReference> result = new ArrayList<>();
List<WebSQLQueryResultAssociation> result = new ArrayList<>();
DBRProgressMonitor monitor = session.getProgressMonitor();
for (DBSEntity entity : entities) {
try {
@@ -377,9 +377,9 @@ public class WebSQLUtils {
? DBStructUtils.readReferences(monitor, entity, attrToBinding)
: DBStructUtils.readAssociations(monitor, entity, attrToBinding);
for (DBSEntityAssociation association : source) {
List<Integer> columnIndex = collectOwnColumnIndex(monitor, association, reverse, attrToIndex);
if (columnIndex != null) {
result.add(new WebSQLQueryResultReference(session, association, reverse, columnIndex));
List<WebSQLReferenceColumnMapping> mapping = collectColumnMapping(monitor, association, reverse, attrToIndex);
if (mapping != null) {
result.add(new WebSQLQueryResultAssociation(session, association, reverse, mapping));
}
}
} catch (DBException e) {
@@ -389,38 +389,49 @@ public class WebSQLUtils {
return result;
}
/**
* Pairs source-side attributes (in our result set) with target-side attributes
*/
@Nullable
private static List<Integer> collectOwnColumnIndex(
private static List<WebSQLReferenceColumnMapping> collectColumnMapping(
@NotNull DBRProgressMonitor monitor,
@NotNull DBSEntityAssociation association,
boolean reverse,
@NotNull Map<DBSEntityAttribute, Integer> attrToIndex
) throws DBException {
DBSEntityReferrer ownSide;
if (reverse) {
DBSEntityConstraint refConstraint = association.getReferencedConstraint();
if (!(refConstraint instanceof DBSEntityReferrer referrer)) {
return null;
}
ownSide = referrer;
} else {
if (!(association instanceof DBSEntityReferrer associationRef)) {
return null;
}
ownSide = associationRef;
}
List<? extends DBSEntityAttributeRef> attrs = ownSide.getAttributeReferences(monitor);
if (attrs == null || attrs.isEmpty()) {
if (!(association instanceof DBSEntityReferrer associationReferrer)) {
return null;
}
List<Integer> indexList = new ArrayList<>(attrs.size());
for (DBSEntityAttributeRef attrRef : attrs) {
Integer idx = attrToIndex.get(attrRef.getAttribute());
if (idx == null) {
DBSEntityConstraint refConstraint = association.getReferencedConstraint();
if (!(refConstraint instanceof DBSEntityReferrer constraintReferrer)) {
return null;
}
DBSEntityReferrer sourceSide = reverse ? constraintReferrer : associationReferrer;
DBSEntityReferrer targetSide = reverse ? associationReferrer : constraintReferrer;
List<? extends DBSEntityAttributeRef> sourceAttrs = sourceSide.getAttributeReferences(monitor);
List<? extends DBSEntityAttributeRef> targetAttrs = targetSide.getAttributeReferences(monitor);
if (sourceAttrs == null || targetAttrs == null
|| sourceAttrs.isEmpty() || sourceAttrs.size() != targetAttrs.size()
) {
return null;
}
List<WebSQLReferenceColumnMapping> mapping = new ArrayList<>(sourceAttrs.size());
for (int i = 0; i < sourceAttrs.size(); i++) {
DBSEntityAttribute sourceAttr = sourceAttrs.get(i).getAttribute();
Integer sourceIdx = sourceAttr == null ? null : attrToIndex.get(sourceAttr);
if (sourceIdx == null) {
return null;
}
indexList.add(idx);
DBSEntityAttribute targetAttr = targetAttrs.get(i).getAttribute();
if (targetAttr == null) {
return null;
}
// getOrdinalPosition() is 1-based. Normalize to 0-based to match sourceColumnIndex
int targetIdx = targetAttr.getOrdinalPosition() - 1;
mapping.add(new WebSQLReferenceColumnMapping(
sourceIdx, sourceAttr.getName(), targetIdx, targetAttr.getName()));
}
return indexList;
return mapping;
}
}
@@ -54,6 +54,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
.dataFetcher("sqlDialectInfo", env ->
getService(env).getDialectInfo(getWebConnection(env))
)
.dataFetcher("sqlResultAssociations", env ->
getService(env).getSqlResultAssociations(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgument(env, "isReference")
)
)
.dataFetcher("sqlListContexts", env ->
getService(env).listContexts(getWebSession(env),
getProjectReference(env),
@@ -232,17 +240,6 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
getDataFilter(env),
getDataFormat(env)
))
.dataFetcher("asyncSqlNavigateForeignKey", env ->
getService(env).asyncNavigateForeignKey(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId"),
new WebSQLResultsRow(getArgument(env, "row")),
getArgumentVal(env, "columnIndex"),
getArgumentVal(env, "associationName"),
CommonUtils.toBoolean(env.getArgument("isReference")),
getDataFormat(env)
))
.dataFetcher("asyncSqlExecuteResults", env ->
getService(env).asyncGetQueryResults(
getWebSession(env), getArgumentVal(env, "taskId")
@@ -353,7 +350,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
}
@Override
public void addServlets(ServletApplication application, DBWServletContext servletContext) throws DBException {
public void addServlets(@NotNull ServletApplication application, @NotNull DBWServletContext servletContext) throws DBException {
servletContext.addServlet(
"sqlResultValueViewer",
new WebSQLResultServlet(application, getServiceImpl()),
@@ -36,8 +36,6 @@ import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDReferenceNavigation;
import org.jkiss.dbeaver.model.data.DBDReferenceUtils;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
@@ -61,7 +59,9 @@ 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.sql.semantics.completion.SQLQueryCompletionContext;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSWrapper;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
@@ -603,72 +603,25 @@ public class WebServiceSQL implements DBWServiceSQL {
return contextInfo.getProcessor().getWebSession().createAndRunAsyncTask("Read data from container " + nodePath, runnable);
}
@NotNull
@Override
public WebAsyncTaskInfo asyncNavigateForeignKey(
public List<WebSQLQueryResultAssociation> getSqlResultAssociations(
@NotNull WebSession webSession,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
@NotNull WebSQLResultsRow row,
int columnIndex,
@NotNull String associationName,
boolean isReference,
@Nullable WebDataFormat dataFormat
) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
try {
monitor.beginTask("Navigate foreign key", 1);
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
DBSEntity sourceEntity = resolveSourceEntity(resultsInfo.getAttributes(), columnIndex);
DBSEntityAssociation association = isReference
? DBStructUtils.findReverseAssociationByName(monitor, sourceEntity, associationName)
: DBStructUtils.findForwardAssociationByName(monitor, sourceEntity, associationName);
WebDBDResultSetDataProvider dataProvider = new WebDBDResultSetDataProvider(
resultsId,
contextInfo,
List.of(row)
);
DBDReferenceNavigation navigation = isReference
? DBDReferenceUtils.resolveReferenceNavigation(monitor, dataProvider, association, dataProvider.getSelectedRows())
: DBDReferenceUtils.resolveAssociationNavigation(monitor, dataProvider, association, dataProvider.getSelectedRows());
if (!(navigation.getTargetEntity() instanceof DBSDataContainer targetDataContainer)) {
throw new DBWebException("Referenced entity '" + navigation.getTargetEntity().getName() + "' is not a data container");
}
WebSQLExecuteInfo executeResults = contextInfo.getProcessor().readDataFromContainer(
contextInfo,
monitor,
targetDataContainer,
null,
WebSQLDataFilter.from(navigation.getTargetFilter()),
dataFormat
);
this.result = executeResults.getStatusMessage();
this.extendedResults = executeResults;
} catch (Throwable e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
return webSession.createAndRunAsyncTask("Navigate foreign key from results " + resultsId, runnable);
}
@NotNull
private DBSEntity resolveSourceEntity(
@NotNull DBDAttributeBinding[] attributes,
int columnIndex
@Nullable Boolean isReference
) throws DBException {
if (columnIndex < 0 || columnIndex >= attributes.length) {
throw new DBWebException("Column index '" + columnIndex + "' is out of range");
DBDAttributeBinding[] attributes = contextInfo.getResults(resultsId).getAttributes();
if (isReference == null) {
// Both forward associations and reverse references
List<WebSQLQueryResultAssociation> associations =
new ArrayList<>(WebSQLUtils.collectAssociations(webSession, attributes));
associations.addAll(WebSQLUtils.collectReferences(webSession, attributes));
return associations;
}
DBSEntityAttribute entityAttribute = attributes[columnIndex].getEntityAttribute();
if (entityAttribute == null) {
throw new DBException("Column [" + attributes[columnIndex].getName() + "] is not bound to any entity");
}
return entityAttribute.getParentObject();
return isReference
? WebSQLUtils.collectReferences(webSession, attributes)
: WebSQLUtils.collectAssociations(webSession, attributes);
}
@NotNull
@@ -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.148.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260803
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.148-SNAPSHOT</version>
<version>1.0.150-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -25,6 +25,7 @@ import io.cloudbeaver.service.DBWServletContext;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.admin.impl.WebAdminLogsServlet;
import io.cloudbeaver.service.admin.impl.WebServiceAdmin;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.utils.CommonUtils;
@@ -205,7 +206,7 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
}
@Override
public void addServlets(CBApplication application, DBWServletContext servletContext) throws DBException {
public void addServlets(@NotNull CBApplication application, @NotNull DBWServletContext servletContext) throws DBException {
if(!application.isMultiuser()) {
return;
}
@@ -19,20 +19,15 @@ package io.cloudbeaver.service.admin.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.registry.*;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.*;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.admin.*;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -44,7 +39,6 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.auth.SMAuthConfiguration;
import org.jkiss.dbeaver.model.auth.SMObjectType;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.rm.RMProjectType;
@@ -596,50 +590,12 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@Override
public boolean configureServer(@NotNull WebSession webSession, @NotNull Map<String, Object> params) throws DBWebException {
try {
CBAppConfig appConfig = new CBAppConfig(CBApplication.getInstance().getAppConfiguration());
CBServerConfig serverConfig = new CBServerConfig();
serverConfig.setServerName(CBApplication.getInstance().getServerName());
serverConfig.setMaxSessionIdleTime(CBApplication.getInstance().getMaxSessionIdleTime());
String adminName = null;
String adminPassword = null;
if (!params.isEmpty()) { // FE can send an empty configuration
var config = new AdminServerConfig(params);
appConfig.setAnonymousAccessEnabled(config.isAnonymousAccessEnabled());
appConfig.setSupportsCustomConnections(config.isCustomConnectionsEnabled());
appConfig.setPublicCredentialsSaveEnabled(config.isPublicCredentialsSaveEnabled());
appConfig.setAdminCredentialsSaveEnabled(config.isAdminCredentialsSaveEnabled());
updateDisabledFeaturesConfig(appConfig, config.getEnabledFeatures());
// custom logic for enabling embedded drivers
updateDisabledDriversConfig(appConfig, config.getDisabledDrivers());
appConfig.setResourceManagerEnabled(config.isResourceManagerEnabled());
appConfig.setSecretManagerEnabled(config.isSecretManagerEnabled());
if (CommonUtils.isEmpty(config.getEnabledAuthProviders())) {
// All of them
appConfig.setEnabledAuthProviders(new String[0]);
} else {
appConfig.setEnabledAuthProviders(config.getEnabledAuthProviders().toArray(new String[0]));
}
appConfig.setDefaultNavigatorSettings(
CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings());
adminName = config.getAdminName();
adminPassword = config.getAdminPassword();
serverConfig.setServerName(config.getServerName());
serverConfig.setMaxSessionIdleTime(config.getSessionExpireTime());
if (config.getForceHttps() != null) {
serverConfig.setForceHttps(config.getForceHttps());
}
if (config.getSupportedHosts() != null) {
serverConfig.setSupportedHosts(config.getSupportedHosts());
}
if (config.getBindSessionToIp() != null) {
serverConfig.setBindSessionToIp(config.getBindSessionToIp());
}
}
CBServerConfigurationMapper<?, ?> mapper = CBApplication.getInstance()
.getServerConfigurationController().createServerConfigurationInputMapper();
CBServerConfigurations configurations = mapper.loadServerConfigurationsFromParams(params);
String adminName = configurations.adminName();
String adminPassword = configurations.adminPassword();
if (CommonUtils.isEmpty(adminName)) {
// Grant admin permissions to the current user
WebUser curUser = webSession.getUser();
@@ -651,7 +607,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
if (CommonUtils.isEmpty(adminName)) {
// Try to get admin name from existing authentications (first one)
if (!authInfoList.isEmpty()) {
adminName = authInfoList.get(0).getUserId();
adminName = authInfoList.getFirst().getUserId();
}
}
if (CommonUtils.isEmpty(adminName)) {
@@ -666,7 +622,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
// Patch configuration by services
for (DBWServiceServerConfigurator wsc : WebServiceRegistry.getInstance().getWebServices(DBWServiceServerConfigurator.class)) {
try {
wsc.configureServer(CBApplication.getInstance(), webSession, serverConfig, appConfig);
wsc.configureServer(CBApplication.getInstance(), webSession, configurations.serverConfig(), configurations.appConfig());
} catch (Exception e) {
log.warn("Error configuring server by web service " + wsc.getClass().getName(), e);
}
@@ -678,8 +634,8 @@ public class WebServiceAdmin implements DBWServiceAdmin {
adminName,
adminPassword,
authInfos,
serverConfig,
appConfig,
configurations.serverConfig(),
configurations.appConfig(),
webSession
);
@@ -687,8 +643,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
if (configurationMode) {
// In config mode we always refresh because admin user doesn't exist yet
webSession.resetUserState();
}
else {
} else {
// Just reload session state
webSession.refreshUserData();
}
@@ -700,46 +655,6 @@ public class WebServiceAdmin implements DBWServiceAdmin {
return true;
}
private void updateDisabledFeaturesConfig(@NotNull CBAppConfig appConfig, @NotNull List<String> enabledFeatures) {
Set<String> enabledIds = new LinkedHashSet<>(enabledFeatures);
appConfig.setEnabledFeatures(enabledFeatures.toArray(new String[0]));
String[] disabledFeatures = ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures().stream()
.map(DBWFeatureSet::getId)
.filter(id -> !enabledIds.contains(id))
.toArray(String[]::new);
appConfig.setDisabledFeatures(disabledFeatures);
}
// we disable embedded drivers by default and enable it in enabled drivers list
// that's why we need so complicated logic for disabling drivers
private void updateDisabledDriversConfig(CBAppConfig appConfig, String[] disabledDriversConfig) {
Set<String> disabledIds = new LinkedHashSet<>(Arrays.asList(disabledDriversConfig));
Set<String> enabledIds = new LinkedHashSet<>(Arrays.asList(appConfig.getEnabledDrivers()));
// remove all disabled embedded drivers from enabled drivers list
enabledIds.removeAll(disabledIds);
// enable embedded driver if it is not in disabled drivers list
for (String driverId : appConfig.getDisabledDrivers()) {
if (disabledIds.contains(driverId)) {
// driver is also disabled
continue;
}
// driver is removed from disabled list
// we need to enable if it is embedded
try {
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
if (driver.isEmbedded()) {
enabledIds.add(driverId);
}
} catch (DBWebException e) {
log.error("Failed to find driver by id", e);
}
}
appConfig.setDisabledDrivers(disabledDriversConfig);
appConfig.setEnabledDrivers(enabledIds.toArray(String[]::new));
}
@Override
public boolean setDefaultNavigatorSettings(@NotNull WebSession webSession, @NotNull DBNBrowseSettings settings) throws DBWebException {
CBApplication.getInstance().getAppConfiguration().setDefaultNavigatorSettings(settings);
@@ -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.148.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260803
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.148-SNAPSHOT</version>
<version>1.0.150-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -36,6 +36,7 @@ public interface DBWServiceAuth extends DBWService {
@WebAction(authRequired = false)
WebAuthStatus authLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -37,6 +37,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
public void bindWiring(DBWBindingContext model) {
model.getQueryType()
.dataFetcher("authLogin", env -> getService(env).authLogin(
GraphQLEndpoint.getServletRequestOrThrow(env),
getWebSession(env, false),
getArgumentVal(env, "provider"),
getArgument(env, "configuration"),
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -67,7 +67,8 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebAuthStatus authLogin(
@NotNull WebSession webSession,
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession inputWebSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
@Nullable Map<String, Object> authParameters,
@@ -75,6 +76,13 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
boolean forceSessionsLogout
) throws DBWebException {
try {
WebSession webSession = inputWebSession;
if (inputWebSession.getUser() == null) {
// Rotate anonymous web sessions during login attempts to prevent session fixation attacks.
webSession = CBApplication.getInstance().getSessionManager()
.rotateSession(httpRequest, inputWebSession);
}
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
//TODO deprecated, use asyncAuthLogin for federated auth, exits for backward compatibility
linkWithActiveUser = linkWithActiveUser && CBApplication.getInstance().getAppConfiguration()
@@ -85,7 +93,8 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
} else {
//run it sync
var authProcessor = new WebSessionAuthProcessor(webSession, smAuthInfo, linkWithActiveUser);
return new WebAuthStatus(smAuthInfo.getAuthStatus(), authProcessor.authenticateSession());
List<WebAuthInfo> authInfos = authProcessor.authenticateSession();
return new WebAuthStatus(smAuthInfo.getAuthStatus(), authInfos);
}
} catch (SMTooManySessionsException e) {
throw new DBWebException("User authentication failed", e.getErrorType(), e);
@@ -95,14 +104,22 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
@Override
@NotNull
public WebAsyncAuthStatus federatedLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull WebSession inputWebSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
boolean linkWithActiveUser,
boolean forceSessionsLogout
) throws DBWebException {
WebSession webSession = inputWebSession;
if (inputWebSession.getUser() == null) {
// Rotate anonymous web sessions during login attempts to prevent session fixation attacks.
webSession = CBApplication.getInstance().getSessionManager()
.rotateSession(httpRequest, inputWebSession);
}
WebAuthProviderDescriptor providerDescriptor = WebAuthProviderRegistry.getInstance().getAuthProvider(providerId);
if (providerDescriptor == null) {
throw new DBWebException("Provider '" + providerId + "' not found");
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.149.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.149-SNAPSHOT</version>
<version>1.0.151-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -59,7 +59,7 @@ public interface DBWServiceDataTransfer extends DBWService {
) throws DBWebException;
@NotNull
@WebAction
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_IMPORT)
WebAsyncTaskInfo asyncImportDataContainer(
@NotNull String processorId,
@NotNull Path path,
@@ -79,7 +79,7 @@ public interface DBWServiceDataTransfer extends DBWService {
* after data transfer.
*/
@NotNull
@WebAction
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_IMPORT)
@Deprecated
Boolean dataTransferRemoveDataFile(@NotNull WebSession session, @NotNull String dataFileId) throws DBWebException;
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -26,12 +26,13 @@ import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferServlet;
import io.cloudbeaver.service.data.transfer.impl.WebServiceDataTransfer;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
/**
* Web service implementation
*/
public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServiceDataTransfer> implements DBWServiceBindingServlet<CBApplication> {
public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServiceDataTransfer> implements DBWServiceBindingServlet<CBApplication<?>> {
public WebServiceBindingDataTransfer() {
super(DBWServiceDataTransfer.class, new WebServiceDataTransfer(), "schema/service.data.transfer.graphqls");
@@ -47,17 +48,17 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
env -> getService(env).getAvailableImportStreamProcessors(getWebSession(env)))
.dataFetcher("dataTransferExportDataFromContainer", env -> getService(env).dataTransferExportDataFromContainer(
WebServiceBindingSQL.getSQLProcessor(env),
getArgument(env, "containerNodePath"),
getArgumentVal(env, "containerNodePath"),
new WebDataTransferParameters(getArgument(env, "parameters"))
))
.dataFetcher("dataTransferExportDataFromResults", env -> getService(env).dataTransferExportDataFromResults(
WebServiceBindingSQL.getSQLContext(env),
getArgument(env, "resultsId"),
getArgumentVal(env, "resultsId"),
new WebDataTransferParameters(getArgument(env, "parameters"))
))
.dataFetcher("dataTransferRemoveDataFile", env -> getService(env).dataTransferRemoveDataFile(
getWebSession(env),
getArgument(env, "dataFileId")
getArgumentVal(env, "dataFileId")
))
.dataFetcher("dataTransferDefaultExportSettings", env -> getService(env).defaultExportSettings())
;
@@ -65,7 +66,7 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
}
@Override
public void addServlets(CBApplication application, DBWServletContext servletContext) throws DBException {
public void addServlets(@NotNull CBApplication<?> application, @NotNull DBWServletContext servletContext) throws DBException {
if (!application.isMultiuser()) {
return;
}
@@ -317,7 +317,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
properties,
producer.getProject());
producer.transferData(monitor, consumer, null, producerSettings, null);
producer.transferData(monitor, consumer, null, producerSettings, null, -1);
consumer.finishTransfer(monitor, false);
}
@@ -374,7 +374,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
databaseMappingContainer.setTarget(dataContainer);
consumer.setContainerMapping(databaseMappingContainer);
try {
producer.transferData(monitor, consumer, processorInstance, producerSettings, null);
producer.transferData(monitor, consumer, processorInstance, producerSettings, null, -1);
if (monitor.isCanceled()) {
throw new DBWebException("Import is canceled");
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.66.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.68.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.66-SNAPSHOT</version>
<version>1.0.68-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -24,6 +24,7 @@ import io.cloudbeaver.service.DBWServletContext;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.fs.impl.WebServiceFS;
import io.cloudbeaver.service.fs.model.WebFSServlet;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.utils.CommonUtils;
@@ -117,7 +118,7 @@ public class WebServiceBindingFS extends WebServiceBindingBase<DBWServiceFS> imp
}
@Override
public void addServlets(CBApplication<?> application, DBWServletContext servletContext) throws DBException {
public void addServlets(@NotNull CBApplication<?> application, @NotNull DBWServletContext servletContext) throws DBException {
if (!application.isMultiuser()) {
return;
}
@@ -128,12 +128,19 @@ public class WebFSServlet extends WebServiceServletBase {
if (baseName == null || baseName.toString().isBlank()) {
throw new DBException("Invalid file name");
}
Path normalizedParent = parent.normalize();
Path resolved = normalizedParent.resolve(baseName.toString()).normalize();
if (!resolved.startsWith(normalizedParent) || resolved.equals(normalizedParent)) {
if (submittedFileName.isBlank()
|| ".".equals(submittedFileName)
|| "..".equals(submittedFileName)
|| submittedFileName.indexOf('/') >= 0
|| submittedFileName.indexOf('\\') >= 0
) {
throw new DBException("Invalid file name");
}
try {
return parent.normalize().resolve(submittedFileName).normalize();
} catch (InvalidPathException e) {
throw new DBException("Invalid file name");
}
return resolved;
}
@Override
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - LDAP
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true
Bundle-Version: 1.0.31.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.33.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ldap.auth</artifactId>
<version>1.0.31-SNAPSHOT</version>
<version>1.0.33-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -252,7 +252,8 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
NamingEnumeration<SearchResult> results = findByFilter(
serviceContext,
ldapSettings,
buildSearchFilter(ldapSettings, userIdentifier),
buildSearchFilter(ldapSettings),
new Object[]{userIdentifier},
searchControls
);
@@ -271,10 +272,22 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
@NotNull LdapSettings ldapSettings,
@NotNull String searchFilter,
@NotNull SearchControls searchControls
) throws DBException {
return findByFilter(serviceContext, ldapSettings, searchFilter, null, searchControls);
}
public NamingEnumeration<SearchResult> findByFilter(
@NotNull DirContext serviceContext,
@NotNull LdapSettings ldapSettings,
@NotNull String searchFilter,
@Nullable Object[] filterArgs,
@NotNull SearchControls searchControls
) throws DBException {
try {
String baseDN = getBaseDN(serviceContext, ldapSettings);
return serviceContext.search(baseDN, searchFilter, searchControls);
return filterArgs == null
? serviceContext.search(baseDN, searchFilter, searchControls)
: serviceContext.search(baseDN, searchFilter, filterArgs, searchControls);
} catch (Exception e) {
throw new DBException("Error finding user DN: " + e.getMessage(), e);
}
@@ -287,8 +300,8 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
return ldapSettings.getBaseDN();
}
private String buildSearchFilter(LdapSettings ldapSettings, String userIdentifier) {
String userFilter = String.format("(%s=%s)", ldapSettings.getLoginAttribute(), userIdentifier);
private String buildSearchFilter(LdapSettings ldapSettings) {
String userFilter = String.format("(%s={0})", ldapSettings.getLoginAttribute());
if (CommonUtils.isNotEmpty(ldapSettings.getFilter())) {
return String.format("(&%s%s)", userFilter, ldapSettings.getFilter());
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.154.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.152-SNAPSHOT</version>
<version>1.0.154-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
Bundle-Version: 1.0.66.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.68.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.model,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.66-SNAPSHOT</version>
<version>1.0.68-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.101.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.103.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.101-SNAPSHOT</version>
<version>1.0.103-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.104.qualifier
Bundle-Release-Date: 20260706
Bundle-Version: 1.0.106.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.104-SNAPSHOT</version>
<version>1.0.106-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -394,18 +394,30 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
}
}
@NotNull
@Override
public void importUsers(@NotNull SMUserImportList userImportList) throws DBException {
try (var dbCon = database.openConnection()) {
importUsers(dbCon, userImportList);
public List<SMUserProvisioning> importUsers(@NotNull SMUserImportList userImportList) throws DBException {
try (Connection dbCon = database.openConnection();
JDBCTransaction txn = new JDBCTransaction(dbCon)) {
try {
List<SMUserProvisioning> importedUsers = importUsers(dbCon, userImportList);
txn.commit();
return importedUsers;
} catch (Exception e) {
txn.rollback();
throw e;
}
} catch (SQLException e) {
log.error("Failed attempt import user: " + e.getMessage());
return List.of();
}
}
protected void importUsers(@NotNull Connection connection, @NotNull SMUserImportList userImportList)
@NotNull
protected List<SMUserProvisioning> importUsers(@NotNull Connection connection, @NotNull SMUserImportList userImportList)
throws DBException, SQLException {
List<SMUserProvisioning> users = userImportList.getUsers();
List<SMUserProvisioning> importedUsers = new ArrayList<>();
Set<String> seen = new HashSet<>();
outer:
for (SMUserProvisioning user : users) {
@@ -436,7 +448,9 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
}
}
insertUser(connection, userId.toLowerCase(), metaParameters, true, authRole);
importedUsers.add(user);
}
return importedUsers;
}
@Override

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