mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
91
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac2d73935 | ||
|
|
dcbf3f4d6d | ||
|
|
861c2eeb3d | ||
|
|
ad3b95f2a7 | ||
|
|
82775f9dc2 | ||
|
|
f95bb8c041 | ||
|
|
134a77e5bd | ||
|
|
2f04cc5bb0 | ||
|
|
d1ab50783e | ||
|
|
a12ab4f2a2 | ||
|
|
b71010958c | ||
|
|
ac2cb461f3 | ||
|
|
0a1616795d | ||
|
|
571882eab0 | ||
|
|
5b8c88c36a | ||
|
|
1906f9d165 | ||
|
|
5e36ef4e27 | ||
|
|
7cd9ae83bc | ||
|
|
bcf4a2b581 | ||
|
|
50f63caa4f | ||
|
|
a68a29aeb0 | ||
|
|
f5c31fbc07 | ||
|
|
aae05e4769 | ||
|
|
39777dc5fa | ||
|
|
4475513341 | ||
|
|
f4b55add99 | ||
|
|
6d13275021 | ||
|
|
2650b4dccd | ||
|
|
50f6835caf | ||
|
|
3e9e71ca6d | ||
|
|
d48cd7a0a7 | ||
|
|
f37f173c49 | ||
|
|
63b25d7cb1 | ||
|
|
67df14033c | ||
|
|
75a64c1c2d | ||
|
|
28fb7f2264 | ||
|
|
5176519fdd | ||
|
|
4d197c9548 | ||
|
|
56daa059a7 | ||
|
|
3491fb42d7 | ||
|
|
bb9872dc2e | ||
|
|
043d55c395 | ||
|
|
b5c6cbfe82 | ||
|
|
152ab83006 | ||
|
|
bea2451776 | ||
|
|
2d0b7d428c | ||
|
|
9078a379b0 | ||
|
|
bc2c204a61 | ||
|
|
2ff806ed7c | ||
|
|
5d2b246d61 | ||
|
|
212623c0a8 | ||
|
|
d08c191a08 | ||
|
|
6b3e911534 | ||
|
|
620a5cb73b | ||
|
|
4cf41da82c | ||
|
|
3844792b84 | ||
|
|
e7f2b8e0a6 | ||
|
|
f2b30d0f1f | ||
|
|
2781ec1eaa | ||
|
|
707cb3ea41 | ||
|
|
5be49b8a7e | ||
|
|
317750ea7d | ||
|
|
5fb7234e24 | ||
|
|
7a93d56459 | ||
|
|
bd1e926929 | ||
|
|
170650e6c2 | ||
|
|
ffc48e1979 | ||
|
|
c4b0dd4650 | ||
|
|
2d955087ea | ||
|
|
b5289c7865 | ||
|
|
740599f41d | ||
|
|
16915940ba | ||
|
|
1c789cf49b | ||
|
|
9474ec9718 | ||
|
|
46a07033ad | ||
|
|
eb907e95f4 | ||
|
|
cb01f6a988 | ||
|
|
b2065ffe62 | ||
|
|
9e8f4b83a6 | ||
|
|
5170fa7dfc | ||
|
|
ad26e0e849 | ||
|
|
eed9cd979e | ||
|
|
1b8ea64cd0 | ||
|
|
fb43b0f567 | ||
|
|
317ba5486a | ||
|
|
4d4049fcd1 | ||
|
|
27cfdb405f | ||
|
|
f1dc4e07bb | ||
|
|
57cc6e4808 | ||
|
|
6bfe58506f | ||
|
|
1d6e5c6ce6 |
@@ -43,6 +43,24 @@ jobs:
|
||||
uses: ./.github/workflows/frontend-lint.yml
|
||||
secrets: inherit
|
||||
|
||||
required-builds:
|
||||
name: Required Builds
|
||||
if: ${{ always() && github.event.pull_request.draft != true }}
|
||||
needs: [build-server, build-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Verify required builds
|
||||
env:
|
||||
SERVER_RESULT: ${{ needs.build-server.result }}
|
||||
FRONTEND_RESULT: ${{ needs.build-frontend.result }}
|
||||
run: |
|
||||
if [[ "$SERVER_RESULT" != "success" || "$FRONTEND_RESULT" != "success" ]]; then
|
||||
echo "Server build: $SERVER_RESULT"
|
||||
echo "Frontend build: $FRONTEND_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# call-frontend-tests:
|
||||
# name: Frontend Unit Tests
|
||||
# needs: call-frontend-build
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# CloudBeaver
|
||||
|
||||
## What is CloudBeaver?
|
||||
|
||||
CloudBeaver is an open-source, web-based database management application. Its modular frontend provides the database navigator, SQL editor, data editor, administration, and related features, and communicates with the CloudBeaver server through GraphQL.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Scope
|
||||
|
||||
- These instructions apply to the frontend under `webapp/` in CloudBeaver Community Edition and to any other CloudBeaver edition or product that shares this frontend architecture.
|
||||
- Do not modify `server/`, `deploy/`, or backend configuration unless the task explicitly requires it. Backend GraphQL schemas are an input to frontend code generation, not part of the default edit scope.
|
||||
- Treat `package.json`, `.yarnrc.yml`, and the frontend CI workflows as the source of truth for tool and runtime versions; do not duplicate their version numbers in documentation.
|
||||
|
||||
### Project map
|
||||
|
||||
- `webapp/package.json`: Yarn Plug'n'Play workspace root and repository-wide frontend scripts.
|
||||
- `webapp/packages/core-*`: shared application foundations, services, UI primitives, state, and infrastructure.
|
||||
- `webapp/packages/plugin-*`: feature modules. A package normally registers services and bootstraps in `src/module.ts` and exposes its public API from `src/index.ts`.
|
||||
- `webapp/packages/product-*`: CE product composition and Vite entry points. `product-default` is the runnable/bundled frontend; `product-default-impl` selects the CE plugin set.
|
||||
- `webapp/packages/core-sdk`: GraphQL operations and generated client types.
|
||||
- `webapp/common-typescript/@dbeaver/*` and `webapp/common-react/@dbeaver/*`: shared DBeaver utilities and React components used by the workspace.
|
||||
- Tests are colocated with source as `*.test.ts` or `*.test.tsx`; styles are usually colocated CSS modules.
|
||||
|
||||
### Stack
|
||||
|
||||
TypeScript ES modules, React, MobX, the project DI/module registry, GraphQL Code Generator, Vite, Yarn workspaces with PnP, Vitest and Testing Library, ESLint, Prettier, CSS Modules, and Tailwind-based theming.
|
||||
|
||||
### Setup and common commands
|
||||
|
||||
Use the Node version configured by frontend CI and the Yarn version pinned in `webapp/package.json`. Run frontend commands from `webapp/` unless noted.
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
cd webapp
|
||||
yarn install --immutable
|
||||
```
|
||||
|
||||
```sh
|
||||
# Start the frontend dev server; a compatible CloudBeaver API must run separately.
|
||||
(cd packages/product-default && yarn dev)
|
||||
|
||||
# From webapp/: repository-wide checks.
|
||||
yarn lint
|
||||
yarn test
|
||||
yarn validate-dependencies
|
||||
|
||||
# Production frontend build.
|
||||
(cd packages/product-default && yarn bundle)
|
||||
```
|
||||
|
||||
For a focused check, use a package's existing script, for example `yarn workspace @cloudbeaver/core-utils test` or `yarn workspace @cloudbeaver/plugin-sql-editor lint`. Do not invent a package script that is absent from its `package.json`.
|
||||
|
||||
### Working conventions
|
||||
|
||||
- Put changes in the smallest package that owns the behavior. Prefer existing `core-*` abstractions and UI primitives before adding cross-package helpers.
|
||||
- Keep dependencies directed from plugins to core: `core-*` packages must never import or depend on `plugin-*` packages. Plugins may use public APIs from core packages and from other plugins, subject to the administration boundary and the no-cycles rule below.
|
||||
- Put administration-only behavior in a separate package whose name ends with `-administration`. Public/non-administration plugins must not import or depend on administration packages.
|
||||
- Keep the workspace package dependency graph acyclic; do not introduce direct or indirect circular dependencies.
|
||||
- Import workspace packages through their public `@cloudbeaver/*` or `@dbeaver/*` exports; never reach into another package's `src/` directory.
|
||||
- When adding a workspace dependency, update the package's `package.json` through Yarn. The configured `ts-project-linker` synchronizes `tsconfig.json` project references during a non-immutable install; edit references manually only if automatic linking cannot complete. Run `yarn validate-dependencies` afterward.
|
||||
- Register services and bootstraps through the package's `src/module.ts`. Change product composition only when enabling or disabling a package for the CE product.
|
||||
- Put user-visible text in the package localization files and follow the neighboring `LocaleService` pattern; do not hardcode UI copy in components.
|
||||
- Add or change GraphQL operations as `.gql` files in `core-sdk`. Regenerate through `yarn workspace @cloudbeaver/core-sdk gql:gen`; never edit the ignored generated `src/sdk.ts` manually.
|
||||
- Preserve valid license headers on existing source files. When modifying an existing source file, update the ending copyright year to the current four-digit calendar year while keeping its original starting year. For new source files, use the template below and replace `<CURRENT_YEAR>` with the current year; never leave the placeholder in committed code. Use the repository ESLint and Prettier configuration rather than manual formatting rules.
|
||||
- Do not edit or commit generated/install artifacts such as `node_modules/`, `.pnp.*`, `lib/`, `dist/`, `coverage/`, or `allure-results/`.
|
||||
- Add focused tests for behavior changes. Before handoff, run lint and tests for affected packages; run the repository-wide checks and product bundle for cross-cutting or product-composition changes.
|
||||
|
||||
### License header for new source files
|
||||
|
||||
```text
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-<CURRENT_YEAR> DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
- Before backend work, read and follow `../dbeaver/AGENTS.md` for inherited Java and Tycho conventions.
|
||||
- Cloudbeaver does not use SWT or Eclipse RCP, and its backend is headless.
|
||||
- Configuration file is generated using `apps/config-generator`. For making changes there, update `config/template/cloudbeaver-base.conf` or use patches (for specific product parameters).
|
||||
- The backend must support at least the following databases: PostgreSQL, MySQL, Oracle, and SQL Server. Support for additional databases is optional.
|
||||
|
||||
### GraphQL API
|
||||
|
||||
- Use consistent names. IDs must use type `ID`; ID arguments and inputs must end with `Id`, for example `projectId`.
|
||||
- Top-level methods follow `{pluginId}{methodName}`, for example `authLogin`, `rmListProjects`, and `navNodeChildren`. Mark new public API with `@since`.
|
||||
- Preserve released APIs: deprecate instead of renaming or removing, include the deprecation version, and remove only after more than one year. EA-only APIs may change before public release.
|
||||
- New arguments and input fields added to a released API must be optional.
|
||||
|
||||
### License header
|
||||
- Please use header from ../dbeaver/docs/license_header.txt for backend source files.
|
||||
@@ -28,130 +28,79 @@ You can see a live demo of CloudBeaver server here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 26.1.3 2026-07-20
|
||||
### 26.2.0 2026-08-31
|
||||
|
||||
- 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:
|
||||
### Changes since 26.1.0
|
||||
|
||||
- AI Assistant:
|
||||
- Added AI Chat to help users generate and fix queries and explore data more easily. It’s integrated with the SQL Editor to run generated queries instantly. All AI features can be turned off in the Server configuration.
|
||||
- Added the ability to use multiple AI configurations for working with the AI chat. Administrators can define tokens, engines and models for OpenAI and Copilot in the Administration panel for each AI profile separately. After that, users can switch between these profiles while using AI features.
|
||||
- Added the ability to cancel a response in the AI Chat to stop response generation when needed.
|
||||
- The "Endpoint" setting was renamed to "API Base URL" for the OpenAI provider.
|
||||
- Added AI engine icons to AI configuration profiles, making it easier to identify AI providers in the Administration panel and AI Chat configuration.
|
||||
- Administration:
|
||||
- Added support for custom startup arguments when running CloudBeaver in a container. Administrators can specify configuration file locations to adapt the application to their environment. (thanks to @MalteHei)
|
||||
- Updated the manifest.webmanifest file to generate dynamically based on the CLOUDBEAVER_ROOT_URI environment variable. (thanks to @houssemexo26)
|
||||
- Added the database version and the driver version to diagnostic logs.
|
||||
- Fixed the issue of fetching groups during LDAP authorization. CloudBeaver now provides all matched groups regardless of fetching errors.
|
||||
- Added the Last Login time to the user form and the user table in the administration part.
|
||||
- Authorization:
|
||||
- Added HTTP header authentication support for GraphQL queries. Users can provide API tokens via Authorization or X-Api-Key headers, allowing external data tools to authenticate without modifying request bodies.
|
||||
- Fixed a bug in the authentication form: the existing password is now required for password changes.
|
||||
- SQL Editor:
|
||||
- Updated the SQL Editor to use real tab characters for indentation. The application displayed tabs as four-space gaps with a distinct visual marker.
|
||||
- Added an ability to view and edit scripts without connecting to the database.
|
||||
- Added the ability to open generated queries right from the Generate SQL window.
|
||||
- Added fuzzy search to SQL Editor auto-completion for object names. The feature allows users to find tables and containers even with typos or incorrect letter ordering.
|
||||
- Added the tooltip about quoting rules for the Bind variables parameters dialog.
|
||||
- Fixed keyword autocompletion for partially typed keywords to avoid redundant letters.
|
||||
- Added the option to upload files for certificates and keys in the SSL authorization configuration.
|
||||
- Fixed an issue causing the application to lose connection to the database after extended uptime. Access is kept automatically during long-running sessions.
|
||||
- Data Editor:
|
||||
- Added the ability to copy-paste multiple cells at once. Pasted values will be distributed across selected cells.
|
||||
- Added an ability to add, duplicate, delete, or set values to NULL in multiple highlighted rows via the Data Editor context menu.
|
||||
- Added the Find and Replace functionality for the Data Editor with the ability to find data by matching case, whole word, or using regular expressions.
|
||||
- Data Editor started to keep the state of column configurations, such as filters, sorting, and ordering, after the reconnect, page refresh, and re-login.
|
||||
- Added key column mark for tables with unique keys in the data grid.
|
||||
- Added more settings to the Generated SQL window, including the ability to select fully qualified names for query or use compact SQL formatting.
|
||||
- Added a DDL generation option to the Data Editor and Result Set context menu.
|
||||
- Fixed copy and paste behavior for multiline and special characters in the Data Editor. Added visual glyphs to indicate line breaks and tabs in the data grid.
|
||||
- Fixed the ability to copy Boolean values in the clipboard in the Data Editor.
|
||||
- Fixed the ability to calculate row count for non-standard SELECT statements. (thanks to @fdcastel)
|
||||
- Navigator tree:
|
||||
- Reorganized the context menu on the connection level to make it more compact.
|
||||
- Added support for special symbols (pipe, comma, and asterisk) for the search field.
|
||||
- Removed the "Folders" setting from the settings list for the Navigator Tree.
|
||||
- Added the References panel to the Data Editor. The panel displays related records from connected tables. Users can explore table relationships directly.
|
||||
- Added the ability to export generated SQL to a .sql file from the Generate SQL dialog.
|
||||
- Fixed copying of large JSON values in the Data Editor - now the full value can be copied.
|
||||
- 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.
|
||||
- Data Transfer:
|
||||
- Added advanced data import settings, including database-specific replace methods, transaction support, bulk loading, and the option to use a separate connection for data import.
|
||||
- Improved memory usage when importing large CSV files to improve the application performance.
|
||||
- Accessibility:
|
||||
- Added the Skip to content option for quick keyboard access to the Navigator Tree, editors, and shortcuts list tab to improve application accessibility.
|
||||
- Improved keyboard navigation for context menu and buttons for Data Editor, SQL Editor, and Navigator tree.
|
||||
- Added the Ctrl + / keyboard shortcut to open the context menu for selected cells in the Data Editor.
|
||||
- Fixed the ability to switch tabs in the Data Editor using the keyboard.
|
||||
- Fixed contrast for elements across different application parts in the light and dark themes to meet WCAG requirements.
|
||||
- Improved accessibility for the search on the Users section in the Administration panel. The application allows focusing on the search field with the Tab key.
|
||||
- Added a loader to the Save button in the Administration panel to improve indication of the long-performing operations.
|
||||
- Improved drag-and-drop in the Navigator Tree for objects by enlarging the auto-scroll trigger areas.
|
||||
- Added the ability to expand and collapse code block elements, including JSON, using the keyboard in the Data Editor.
|
||||
- Fixed shortcuts behavior for AZERTY keyboards. Now shortcuts like Ctrl/Cmd + Z work correctly.
|
||||
- Added the ability to use the Enter or space keys to turn checkboxes on/off in the connection properties tab.
|
||||
- General:
|
||||
- Marked icons for non-saved (temporary) scripts with the hourglass symbol to add visual distinction from the saved ones.
|
||||
- Added the ability to set to NULL any value in the Driver Properties.
|
||||
- Fixed custom driver properties display for the connection page.
|
||||
- Fixed data export in SQL format for values containing single quotes.
|
||||
- Databases and drivers:
|
||||
- ClickHouse:
|
||||
- Fixed the display of Array(JSON) types in the data grid.
|
||||
- Updated driver version to 0.9.7.
|
||||
- Fixed IP address display in the Data Editor by removing the leading slash.
|
||||
- Added support for the "prompt=false" connection property for DB2 for IBM i to turn off interactive prompts and prevent related errors.
|
||||
- DuckDB:
|
||||
- Fixed map rendering for GEOMETRY types with CRS parameters.
|
||||
- Fixed geometry type display for database versions > 1.5.
|
||||
- H2 database:
|
||||
- Added new embedded driver version 2.4.2.
|
||||
- Updated server driver to version 2.4.2.
|
||||
- Firebird: Expanded database-specific SQL dialect coverage for the SQL Editor. Added more keywords, built-in functions, and SQL generators.
|
||||
- MySQL: Fixed query boundaries recognition in the SQL Editor for scripts with the DELIMITER keyword.
|
||||
- PostgreSQL:
|
||||
- Updated driver to version 42.7.11
|
||||
- Fixed missing INOUT parameters in CHECK generation for procedures and functions.
|
||||
- Fixed handling of 24:xx values in time columns.
|
||||
- Fixed an error when connecting via URL if the database name contains a hyphen.
|
||||
- SQL Server:
|
||||
- Fixed DDL generation for external tables.
|
||||
- Fixed the database connectivity issue after the Java update.
|
||||
- Added connection types to visually distinguish database connections using background colors across the Database Navigator and application UI.
|
||||
- Improved SQL autocomplete suggestion ordering for object names. Now suggestions are sorted alphabetically.
|
||||
- Added a Project Info tab containing the project description to the project context menu.
|
||||
- Fixed synchronization between devices. Users see updated connections and folders in other devices without manual refreshes.
|
||||
- Databases:
|
||||
- ClickHouse
|
||||
- The driver was updated to version 0.10.0
|
||||
- Improved JSON display in the Value panel
|
||||
- Added support for the Map data type visualization for the Data Editor
|
||||
- The Databend driver was updated to version 0.4.8.
|
||||
- LibSQL: Added support for database views.
|
||||
- MySQL: Fixed support for comments starting with '#' symbol in the SQL Editor.
|
||||
- PostgreSQL
|
||||
- Updated the driver to version 42.7.13.
|
||||
- Added a Show full DDL option for schemas and tables to generate complete DDL, including objects within a schema and object comments and privileges.
|
||||
- Fixed an error that occurred when applying filters to database nodes
|
||||
- Fixed generated calls for procedures with OUT parameters
|
||||
- Security:
|
||||
- Added an administrative setting to restrict SSH tunneling capabilities. Administrators can now limit tunnel configuration to authorized users, reducing the risk of unauthorized network access.
|
||||
- Fixed the critical vulnerability (CVE-2025-62718) in the axios library. The library was updated to version 1.15.0.
|
||||
- Fixed the high vulnerability (CVE-2026-4800) in the lodash library. The library was updated to version 4.18.0.
|
||||
- Fixed the high vulnerability (CVE-2026-39363) in the vite library. The library was updated to version 7.3.2.
|
||||
- Fixed the high vulnerability (CVE-2026-42035) in the axios library. The library was updated to version 1.15.1.
|
||||
- Fixed the high vulnerability (CVE-2026-3505) in the bcpg-jdk18on library. The library was updated to version 1.84.0.
|
||||
- Fixed the high vulnerability (CVE-2026-33671) in the picomatch library. The library was updated to version 2.3.2.
|
||||
- Fixed the high vulnerability (CVE-2026-33943) in the happy-dom library. The library was updated to version 20.8.8.
|
||||
- Fixed the high vulnerability (CVE-2025-45141) in the tar library. The library was updated to version 7.5.11.
|
||||
- Fixed the high vulnerability (CVE-2026-27148) in the storybook library. The library was updated to version 10.2.10.
|
||||
- Fixed the high vulnerability (CVE-2026-27606) in the rollup library. The library was updated to version 4.59.0.
|
||||
- Fixed the high vulnerability (CVE-2026-27904) in the minimatch library. The library was updated to version 3.1.3.
|
||||
- Fixed the high vulnerability (CVE-2026-27959) in the koa library. The library was updated to version 2.16.4.
|
||||
- Fixed the high vulnerability (CVE-2026-32141) in the flatted library. The library was updated to version 4.4.0.
|
||||
- Fixed the medium vulnerability (CVE-2026-41305) in the postcss library. The library was updated to version 8.5.14.
|
||||
- Removed the ability to rename SQL scripts that start with dots to prevent them from being moved outside the Scripts directory into the root Tree.
|
||||
- Added a new "Auto-create users" setting for the reverse proxy provider, allowing new user creation on login. It is enabled by default.
|
||||
- Fixed a security flaw allowing unauthorized users to test database connections. The application now requires proper permissions for this action, preventing malicious requests and protecting server files.
|
||||
- Fixed a security flaw in the LDAP authentication process. The application validates the username input to prevent unauthorized access and protect directory data.
|
||||
- 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.
|
||||
- Updated session handling to prevent session fixation vulnerabilities. The application generates new session identifiers upon login.
|
||||
- Updated the PostgreSQL JDBC driver to version 42.7.13, including a fix for a high-severity security vulnerability (CVE-2026-54291).
|
||||
- Fixed the critical vulnerability (CVE-2026-59873) in the tar library. The library was updated to version 7.5.19.
|
||||
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4)
|
||||
- Fixed the high vulnerability (CVE-2026-10050) in the jetty-security library. The library was updated to version 12.1.11.
|
||||
- Fixed the high vulnerability (CVE-2026-14257) in the brace-expansion library. The library was updated to version 5.0.8.
|
||||
- Fixed the high vulnerability (GHSA-r28c-9q8g-f849) in the postcss library. The library was updated to version 8.5.18.
|
||||
- Fixed the high vulnerability (GHSA-pm4m-ph32-ghv5) in the js-yaml library. The library was updated to version 5.2.2.
|
||||
- Fixed the high vulnerability (CVE-2026-59880) in the immutable library. The library was updated to version 5.1.8.
|
||||
- Fixed the high vulnerability (CVE-2026-59879) in the fast-uri library. The library was updated to version 3.1.3.
|
||||
- Fixed the high vulnerability (CVE-2026-13311) in the shell-quote library. The library was updated to version 1.9.0.
|
||||
- Fixed the high vulnerability (GHSA-gcfj-64vw-6mp9) in the axios library. The library was updated to version 1.18.0.
|
||||
- Fixed the high vulnerability (CVE-2026-67213) in the nanoid library. The library was updated to version 3.3.17.
|
||||
- Fixed the high vulnerability (CVE-2025-71329) in the less library. The library was updated to version 4.7.0.
|
||||
- Fixed the high vulnerability (CVE-2026-13697) in the undici library. The library was updated to version 8.9.0.
|
||||
- Fixed the high vulnerability (CVE-2026-18446) in the fast-uri library. The library was updated to version 4.1.2.
|
||||
- Fixed the high vulnerability (CVE-2026-69152) in the brace-expansion library. The library was updated to version 5.0.9.
|
||||
- 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.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.13.2</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -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.16.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.18.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.16-SNAPSHOT</version>
|
||||
<version>1.0.18-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,21 +3,20 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.107.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.109.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.sm;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.event;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.nio;visibility:=reexport,
|
||||
org.jkiss.dbeaver.registry;visibility:=reexport,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
org.jkiss.dbeaver.net.ssh;visibility:=reexport,
|
||||
org.jkiss.bundle.logback;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
jakarta.servlet-api;bundle-version:="6.0.0";visibility:=reexport
|
||||
jakarta.servlet-api;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.auth,
|
||||
io.cloudbeaver.auth.provider,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.107-SNAPSHOT</version>
|
||||
<version>1.0.109-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.
|
||||
@@ -20,6 +20,7 @@ import graphql.ErrorClassification;
|
||||
import graphql.ErrorType;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.language.SourceLocation;
|
||||
import org.jkiss.dbeaver.DBDatabaseException;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.sql.SQLState;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -146,19 +147,22 @@ public class DBWebException extends DBException implements GraphQLError {
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
} else if (cause.getMessage() == null && cause.getCause() != null) {
|
||||
return message + ":\n" + cause.getCause().getClass().getSimpleName();
|
||||
}
|
||||
// Preserve enriched web/database messages, but unwrap generic exceptions to their most specific cause.
|
||||
Throwable messageCause = cause instanceof DBWebException || cause instanceof DBDatabaseException
|
||||
? cause
|
||||
: CommonUtils.getRootCause(cause);
|
||||
String causeMessage = messageCause.getMessage();
|
||||
if (CommonUtils.isEmpty(causeMessage)) {
|
||||
causeMessage = messageCause.getClass().getSimpleName();
|
||||
}
|
||||
if (CommonUtils.isEmpty(message)) {
|
||||
if (cause.getMessage() != null) {
|
||||
return cause.getMessage();
|
||||
}
|
||||
return cause.getClass().getName();
|
||||
return causeMessage;
|
||||
}
|
||||
if (CommonUtils.equalObjects(message, cause.getMessage())) {
|
||||
if (CommonUtils.equalObjects(message, causeMessage)) {
|
||||
return message;
|
||||
}
|
||||
return message + ":\n" + cause.getMessage();
|
||||
return message + ":\n" + causeMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -135,8 +135,11 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
if (registryIsLoaded) {
|
||||
return;
|
||||
}
|
||||
getDataSourceRegistry().getDataSources().forEach(this::addConnection);
|
||||
Throwable lastError = getDataSourceRegistry().getLastError();
|
||||
DBPDataSourceRegistry dataSourceRegistry = getDataSourceRegistry();
|
||||
// Connection node paths are resolved through the navigator model, so materialize its lazy database branch first.
|
||||
webSession.initializeProjectNavigator(this);
|
||||
dataSourceRegistry.getDataSources().forEach(this::addConnection);
|
||||
Throwable lastError = dataSourceRegistry.getLastError();
|
||||
if (lastError != null) {
|
||||
webSession.addSessionError(lastError);
|
||||
log.error("Error refreshing connections from project '" + getId() + "'", lastError);
|
||||
@@ -237,6 +240,18 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
|
||||
var sendDataSourceUpdatedEvent = false;
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
Map<String, DataSourceDescriptor> dataSourceSnapshots = new HashMap<>();
|
||||
if (WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
for (String dsId : event.getDataSourceIds()) {
|
||||
DBPDataSourceContainer dataSource = registry.getDataSource(dsId);
|
||||
if (dataSource instanceof DataSourceDescriptor descriptor) {
|
||||
DBPDataSourceContainer snapshot = registry.createDataSource(descriptor);
|
||||
if (snapshot instanceof DataSourceDescriptor snapshotDescriptor) {
|
||||
dataSourceSnapshots.put(dsId, snapshotDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
registry.refreshConfig(event.getDataSourceIds());
|
||||
}
|
||||
@@ -251,6 +266,10 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
case WSDataSourceEvent.UPDATED -> {
|
||||
DataSourceDescriptor snapshot = dataSourceSnapshots.get(dsId);
|
||||
if (snapshot != null && !event.getProperty().hasEffectiveChanges(snapshot, ds)) {
|
||||
continue;
|
||||
}
|
||||
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds, true);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ public class WebConnectionInfo {
|
||||
private static final String FEATURE_PROVIDED = "provided";
|
||||
private static final String FEATURE_MANAGEABLE = "manageable";
|
||||
|
||||
private static final String FEATURE_RESTRICT_DATA_EDIT = "restrictDataEdit";
|
||||
private static final String FEATURE_RESTRICT_SCRIPT_EXECUTE = "restrictScriptExecute";
|
||||
private static final String FEATURE_RESTRICT_DATA_IMPORT = "restrictDataImport";
|
||||
private static final String FEATURE_RESTRICT_METADATA_EDIT = "restrictMetadataEdit";
|
||||
|
||||
private static final String TOOL_SESSION_MANAGER = "sessionManager";
|
||||
|
||||
private final WebSession session;
|
||||
@@ -275,6 +280,18 @@ public class WebConnectionInfo {
|
||||
if (dataSourceContainer.isConnectionReadOnly()) {
|
||||
features.add(FEATURE_READ_ONLY);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) {
|
||||
features.add(FEATURE_RESTRICT_DATA_EDIT);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) {
|
||||
features.add(FEATURE_RESTRICT_SCRIPT_EXECUTE);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) {
|
||||
features.add(FEATURE_RESTRICT_DATA_IMPORT);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) {
|
||||
features.add(FEATURE_RESTRICT_METADATA_EDIT);
|
||||
}
|
||||
if (dataSourceContainer.isProvided()) {
|
||||
features.add(FEATURE_PROVIDED);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.impl.ProviderPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.meta.PropertyConstraints;
|
||||
import org.jkiss.dbeaver.model.meta.PropertyLength;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
|
||||
@@ -42,63 +43,58 @@ import java.util.*;
|
||||
* Web connection info
|
||||
*/
|
||||
public class WebPropertyInfo {
|
||||
private WebSession session;
|
||||
private DBPPropertyDescriptor property;
|
||||
private DBPPropertySource propertySource;
|
||||
private boolean showProtected;
|
||||
@NotNull
|
||||
private final WebSession session;
|
||||
@NotNull
|
||||
private final DBPPropertyDescriptor property;
|
||||
@Nullable
|
||||
private final DBPPropertySource propertySource;
|
||||
|
||||
private Object[] validValues;
|
||||
|
||||
private String[] supportedConfigurationTypes = new String[0];
|
||||
|
||||
private Object defaultValue;
|
||||
|
||||
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property, DBPPropertySource propertySource) {
|
||||
public WebPropertyInfo(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBPPropertyDescriptor property,
|
||||
@Nullable DBPPropertySource propertySource
|
||||
) {
|
||||
this.session = session;
|
||||
this.property = property;
|
||||
this.propertySource = propertySource;
|
||||
}
|
||||
|
||||
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property) {
|
||||
this.session = session;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public boolean isShowProtected() {
|
||||
return showProtected;
|
||||
}
|
||||
|
||||
public void setShowProtected(boolean showProtected) {
|
||||
this.showProtected = showProtected;
|
||||
public WebPropertyInfo(@NotNull WebSession session, @NotNull DBPPropertyDescriptor property) {
|
||||
this(session, property, null);
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// General properties
|
||||
///////////////////////////////////
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getId() {
|
||||
return CommonUtils.toString(property.getId());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getDisplayName() {
|
||||
if (property instanceof DBPNamedObjectLocalized) {
|
||||
return ((DBPNamedObjectLocalized) property).getLocalizedName(session.getLocale());
|
||||
if (property instanceof DBPNamedObjectLocalized localized) {
|
||||
return localized.getLocalizedName(session.getLocale());
|
||||
} else {
|
||||
return property.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getDescription() {
|
||||
if (property instanceof DBPObjectWithDescriptionLocalized) {
|
||||
return ((DBPObjectWithDescriptionLocalized) property).getLocalizedDescription(session.getLocale());
|
||||
if (property instanceof DBPObjectWithDescriptionLocalized localized) {
|
||||
return localized.getLocalizedDescription(session.getLocale());
|
||||
} else {
|
||||
return property.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getHint() {
|
||||
return property.getHint();
|
||||
@@ -109,38 +105,53 @@ public class WebPropertyInfo {
|
||||
return property instanceof ObjectPropertyDescriptor ? ((ObjectPropertyDescriptor) property).getOrderNumber() : -1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getCategory() {
|
||||
return property.getCategory();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getType() {
|
||||
return getDataType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getDataType() {
|
||||
Class<?> dataType = property.getDataType();
|
||||
return dataType == null ? null : dataType.getSimpleName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public PropertyLength getLength() {
|
||||
return property.getLength();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object getDefaultValue() throws DBException {
|
||||
var defaultValue = property.getDefaultValue() == null ? this.defaultValue : property.getDefaultValue();
|
||||
return defaultValue == null ? getValue() : defaultValue;
|
||||
Object defaultValue = property.getDefaultValue();
|
||||
if (property instanceof ObjectPropertyDescriptor && defaultValue == null) {
|
||||
return getValue();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public PropertyConstraints getConstraints() {
|
||||
return property instanceof ObjectPropertyDescriptor descriptor ? descriptor.getConstraints() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object getValue() throws DBException {
|
||||
Object value = propertySource == null ? null : propertySource.getPropertyValue(session.getProgressMonitor(), property.getId());
|
||||
if (property instanceof ObjectPropertyDescriptor opd &&
|
||||
(!showProtected && opd.isPassword() || opd.isHidden())
|
||||
(opd.isPassword() || opd.isHidden())
|
||||
) {
|
||||
return maskValue(value);
|
||||
} else if (property instanceof PropertyDescriptor pd) {
|
||||
@@ -152,6 +163,7 @@ public class WebPropertyInfo {
|
||||
return value == null ? null : makePropertyValue(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object[] getValidValues() {
|
||||
if (property instanceof IPropertyValueListProvider) {
|
||||
@@ -164,17 +176,18 @@ public class WebPropertyInfo {
|
||||
}
|
||||
return validValues;
|
||||
}
|
||||
return validValues;
|
||||
}
|
||||
return validValues;
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String[] getFeatures() {
|
||||
String[] features = property.getFeatures();
|
||||
return features == null ? new String[0] : features;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String[] getSupportedConfigurationTypes() {
|
||||
if (property instanceof ProviderPropertyDescriptor) {
|
||||
@@ -182,7 +195,7 @@ public class WebPropertyInfo {
|
||||
.map(DBPDriverConfigurationType::toString)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
return supportedConfigurationTypes;
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -203,12 +216,14 @@ public class WebPropertyInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return CommonUtils.toString(property.getId());
|
||||
}
|
||||
|
||||
private Object makePropertyValue(Object value) {
|
||||
@Nullable
|
||||
private Object makePropertyValue(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
// } else if (value instanceof DBSObject) {
|
||||
@@ -290,21 +305,6 @@ public class WebPropertyInfo {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setDefaultValue(String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setValidValues(Object[] validValues) {
|
||||
this.validValues = validValues;
|
||||
}
|
||||
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setSupportedConfigurationTypes(String[] supportedConfigurationTypes) {
|
||||
this.supportedConfigurationTypes = supportedConfigurationTypes;
|
||||
}
|
||||
|
||||
public record Condition(@NotNull String expression, @NotNull Type conditionType) {
|
||||
public enum Type {
|
||||
HIDE,
|
||||
|
||||
+18
-2
@@ -1028,8 +1028,24 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
try {
|
||||
while (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1);
|
||||
Path targetPath = projectPath.resolve(resourcePath).normalize();
|
||||
if (!targetPath.startsWith(projectPath)) {
|
||||
throw new DBException("Invalid resource path");
|
||||
// hack for custom path implementations that returns / as root path
|
||||
Iterator<Path> projectParts = projectPath.iterator();
|
||||
Iterator<Path> targetParts = targetPath.iterator();
|
||||
Iterator<Path> unnormalizedTargetParts = projectPath.resolve(resourcePath).iterator();
|
||||
while (projectParts.hasNext()) {
|
||||
if (!targetParts.hasNext()) {
|
||||
throw new DBException("Invalid resource path");
|
||||
}
|
||||
Path projectPart = projectParts.next();
|
||||
Path targetPart = targetParts.next();
|
||||
unnormalizedTargetParts.next();
|
||||
if (!projectPart.equals(targetPart)) {
|
||||
throw new DBException("Invalid resource path");
|
||||
}
|
||||
}
|
||||
// validating unnormalized target parts to avoid invalid characters in resource names
|
||||
while (unnormalizedTargetParts.hasNext()) {
|
||||
GeneralUtils.validateResourceNameUnconditionally(unnormalizedTargetParts.next().toString());
|
||||
}
|
||||
return targetPath;
|
||||
} catch (InvalidPathException e) {
|
||||
|
||||
+11
@@ -155,6 +155,17 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes user permissions, teams and accessible projects.
|
||||
* <p>
|
||||
* Unlike {@link #refreshUserData()} this method must not re-create heavyweight session state
|
||||
* (navigator model, session projects, connection caches), so it is safe to call
|
||||
* for foreign sessions on server-wide events.
|
||||
*/
|
||||
public void refreshUserPermissions() {
|
||||
refreshUserData();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SMAuthSpace getSessionSpace() {
|
||||
|
||||
+63
-2
@@ -262,12 +262,54 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
@Override
|
||||
public void refreshUserData() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
refreshUserPermissions();
|
||||
|
||||
initNavigatorModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshUserPermissions() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
if (getUserId() == null && globalProject != null) {
|
||||
// refreshSessionAuth() updates accessible connections for named users only,
|
||||
// for anonymous sessions this was done by initNavigatorModel()
|
||||
globalProject.refreshAccessibleConnectionIds();
|
||||
}
|
||||
syncSessionProjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/removes only those session projects whose accessibility actually changed.
|
||||
* <p>
|
||||
* Unlike {@link #initNavigatorModel()} this keeps existing projects, and therefore
|
||||
* their live connections and SQL contexts, intact.
|
||||
*/
|
||||
private void syncSessionProjects() {
|
||||
if (getNavigatorModel() == null) {
|
||||
// model was never initialized for this session - nothing to sync
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Map<String, RMProject> actualProjects = new LinkedHashMap<>();
|
||||
for (RMProject rmProject : getRmController().listAccessibleProjects()) {
|
||||
actualProjects.put(rmProject.getId(), rmProject);
|
||||
}
|
||||
for (WebSessionProjectImpl project : new ArrayList<>(getWorkspace().getProjects())) {
|
||||
if (actualProjects.remove(project.getId()) == null && !project.isInMemory()) {
|
||||
// in-memory (anonymous) projects are never listed by RM
|
||||
deleteSessionProject(project);
|
||||
}
|
||||
}
|
||||
for (RMProject rmProject : actualProjects.values()) {
|
||||
createWebProject(rmProject);
|
||||
}
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
log.error("Error synchronizing accessible projects", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: for admin use only
|
||||
public void resetUserState(boolean needResetUserCache) throws DBException {
|
||||
clearAuthTokens();
|
||||
@@ -460,6 +502,25 @@ public class WebSession extends BaseWebSession
|
||||
navigatorModelLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the project's lazy database branch while preventing the navigator model from being replaced.
|
||||
*/
|
||||
public void initializeProjectNavigator(@NotNull WebSessionProjectImpl project) {
|
||||
navigatorModelLock.readLock().lock();
|
||||
try {
|
||||
if (navigatorModel == null) {
|
||||
return;
|
||||
}
|
||||
var projectNode = navigatorModel.getRoot().getProjectNode(project);
|
||||
if (projectNode != null) {
|
||||
projectNode.getDatabases();
|
||||
}
|
||||
} finally {
|
||||
navigatorModelLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and clears progress messages
|
||||
*/
|
||||
|
||||
+6
@@ -59,6 +59,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
private final boolean isPrivate;
|
||||
private final boolean isAuthHidden;
|
||||
private final boolean isCaseInsensitive;
|
||||
private final boolean sameTabRedirectOnLogout;
|
||||
private final boolean serviceProvider;
|
||||
private final String[] requiredFeatures;
|
||||
private final boolean isRequired;
|
||||
@@ -75,6 +76,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_REQUIRED));
|
||||
this.isAuthHidden = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_AUTH_HIDDEN));
|
||||
this.isCaseInsensitive = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_CASE_INSENSITIVE));
|
||||
this.sameTabRedirectOnLogout = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SAME_TAB_LOGOUT_REDIRECT));
|
||||
this.serviceProvider = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SERVICE_PROVIDER));
|
||||
|
||||
for (IConfigurationElement cfgElement : cfg.getChildren(WebRegistryConstant.TAG_CONFIGURATION)) {
|
||||
@@ -129,6 +131,10 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
return trusted;
|
||||
}
|
||||
|
||||
public boolean isSameTabRedirectOnLogout() {
|
||||
return sameTabRedirectOnLogout;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ public class WebRegistryConstant {
|
||||
public static final String ATTR_REQUIRED = "required";
|
||||
public static final String ATTR_AUTH_HIDDEN = "authHidden";
|
||||
public static final String ATTR_CASE_INSENSITIVE = "caseInsensitive";
|
||||
public static final String ATTR_SAME_TAB_LOGOUT_REDIRECT = "sameTabRedirectOnLogout";
|
||||
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
|
||||
public static final String ATTR_CATEGORIES = "categories";
|
||||
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -76,7 +76,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
// Remove temp folder
|
||||
if (tempFolder != null) {
|
||||
|
||||
if (!ContentUtils.deleteFileRecursive(tempFolder.toFile())) {
|
||||
if (!ContentUtils.deleteFileRecursive(tempFolder)) {
|
||||
log.warn("Can't delete temp folder '" + tempFolder.toAbsolutePath() + "'");
|
||||
}
|
||||
tempFolder = null;
|
||||
|
||||
@@ -235,6 +235,14 @@ public class ServletAppUtils {
|
||||
response.addCookie(sessionCookie);
|
||||
}
|
||||
|
||||
public static String getHeaderOrLegacy(HttpServletRequest request, String headerName, String legacyHeaderName) {
|
||||
String value = request.getHeader(headerName);
|
||||
if (CommonUtils.isEmpty(value)) {
|
||||
value = request.getHeader(legacyHeaderName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getRequestCookie(HttpServletRequest request, String cookieName) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
|
||||
@@ -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.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</extension>
|
||||
|
||||
<extension point="io.cloudbeaver.feature">
|
||||
<feature id="ai" label="AI Integration" description="Enables AI Chat in the application."/>
|
||||
<feature id="ai" label="AI Integration" description="Enables AI Chat in the application." enabledByDefault="true"/>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.application">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<version>26.2.1-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.152.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.154.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<driver id="generic:trino_jdbc"/>
|
||||
<driver id="duckdb:duckdb_jdbc"/>
|
||||
<driver id="generic:kyuubi_hive"/>
|
||||
<driver id="databend:databend"/>
|
||||
<driver id="databend:databend_jdbc"/>
|
||||
</extension>
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</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 Community
|
||||
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
|
||||
Bundle-Version: 26.1.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.5-SNAPSHOT</version>
|
||||
<version>26.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Core API
|
||||
####################################################
|
||||
|
||||
extend type ServerConfig {
|
||||
serverURL: String! @deprecated
|
||||
rootURI: String!
|
||||
|
||||
+1
@@ -487,6 +487,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
}
|
||||
}
|
||||
appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures()));
|
||||
appConfigProperties.put("disabledFeatures", Arrays.asList(appConfig.getDisabledFeatures()));
|
||||
if (appConfig.getEnabledAuthProviders() != null) {
|
||||
appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
|
||||
}
|
||||
|
||||
@@ -3,16 +3,11 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 26.1.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
|
||||
org.jkiss.dbeaver.data.gis,
|
||||
org.jkiss.bundle.jetty.server;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
Require-Bundle: org.jkiss.bundle.jetty.server;visibility:=reexport,
|
||||
io.cloudbeaver.model;visibility:=reexport,
|
||||
io.cloudbeaver.model.cli,
|
||||
io.cloudbeaver.service.security;visibility:=reexport
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<version>26.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Any object (JSON)
|
||||
####################################################
|
||||
# Base schema
|
||||
####################################################
|
||||
|
||||
"Any object (JSON)"
|
||||
scalar Object
|
||||
# Date/Time
|
||||
"Date/Time"
|
||||
scalar DateTime
|
||||
scalar Date
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ type ObjectPropertyInfo {
|
||||
validValues: [ Object ]
|
||||
"Default property value"
|
||||
defaultValue: Object
|
||||
"Constraints for the property value"
|
||||
constraints: PropertyConstraints @since(version: "26.2.0")
|
||||
"Property value length"
|
||||
length: ObjectPropertyLength!
|
||||
"List of supported features (e.g., system, hidden, inherited, foreign, expensive)"
|
||||
@@ -41,6 +43,13 @@ type ObjectPropertyInfo {
|
||||
conditions: [Condition!] @since(version: "25.0.1")
|
||||
}
|
||||
|
||||
type PropertyConstraints @since(version: "26.2.0") {
|
||||
"Minimum allowed value for numeric properties"
|
||||
min: Float
|
||||
"Maximum allowed value for numeric properties"
|
||||
max: Float
|
||||
}
|
||||
|
||||
enum ObjectPropertyLength {
|
||||
"1 character"
|
||||
TINY,
|
||||
@@ -556,7 +565,7 @@ type ConnectionInfo {
|
||||
supportedDataFormats: [ ResultDataFormat! ]!
|
||||
configurationType: DriverConfigurationType
|
||||
|
||||
#Access properties
|
||||
"Access properties"
|
||||
canViewSettings: Boolean!
|
||||
canEdit: Boolean!
|
||||
canDelete: Boolean!
|
||||
@@ -737,7 +746,7 @@ input ConnectionConfig {
|
||||
"ID of database driver"
|
||||
driverId: ID
|
||||
|
||||
# Custom connection parameters (all optional)
|
||||
"Custom connection parameters (all optional)"
|
||||
host: String
|
||||
port: String
|
||||
serverName: String
|
||||
@@ -924,4 +933,3 @@ extend type Mutation {
|
||||
"Get async task info by ID"
|
||||
asyncTaskInfo(id: String!, removeOnFinish: Boolean!): AsyncTaskInfo!
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
####################################################
|
||||
# Datasource events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
|
||||
"Datasource created"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# SQL events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
"Database output log updated (e.g., DBMS in Oracle)"
|
||||
cb_database_output_log_updated,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Nodes, objects and properties
|
||||
####################################################
|
||||
|
||||
type ObjectDescriptor {
|
||||
id: Int
|
||||
@@ -143,7 +145,7 @@ extend type Query {
|
||||
"Refreshes node based on the node path"
|
||||
navRefreshNode( nodePath: ID! ): Boolean @deprecated # Use navReloadNode method from Mutation (24.2.4)
|
||||
|
||||
# contextId currently not using
|
||||
"contextId currently not using"
|
||||
navGetStructContainers( projectId: ID, connectionId: ID!, contextId: ID, catalog: ID ): DatabaseStructContainers!
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ type SQLCompletionProposal {
|
||||
# Data read / SQL execution
|
||||
####################################################
|
||||
|
||||
# SQL context must be created for each SQL editor
|
||||
"SQL context must be created for each SQL editor"
|
||||
type SQLContextInfo {
|
||||
|
||||
id: ID!
|
||||
@@ -153,10 +153,12 @@ type SQLResultSet {
|
||||
"Returns list of rows in the result set. Each row contains data and metadata"
|
||||
rowsWithMetaData: [SQLResultRowMetaData] @since(version: "23.3.5")
|
||||
|
||||
# True means that resultset was generated by single entity query
|
||||
# New rows can be added, old rows can be deleted
|
||||
"""
|
||||
True means that resultset was generated by single entity query
|
||||
New rows can be added, old rows can be deleted
|
||||
"""
|
||||
singleEntity: Boolean!
|
||||
# server always returns hasMoreData = false
|
||||
"server always returns hasMoreData = false"
|
||||
hasMoreData: Boolean!
|
||||
"Identifies if result set has row identifier. If true then it is possible update data or load LOB files"
|
||||
hasRowIdentifier: Boolean! @deprecated (reason: "use rowIdentifierState (25.3.4)")
|
||||
@@ -213,7 +215,7 @@ type SQLQueryResults {
|
||||
updateRowCount: Float
|
||||
sourceQuery: String
|
||||
|
||||
# Actual data format of this result
|
||||
"Actual data format of this result"
|
||||
dataFormat: ResultDataFormat
|
||||
|
||||
resultSet: SQLResultSet
|
||||
@@ -221,17 +223,17 @@ type SQLQueryResults {
|
||||
|
||||
type SQLExecuteInfo {
|
||||
|
||||
# Status message
|
||||
"Status message"
|
||||
statusMessage: String
|
||||
# Execute time (ms)
|
||||
"Execute time (ms)"
|
||||
duration: Int!
|
||||
# Actual conditions applied to query
|
||||
"Actual conditions applied to query"
|
||||
filterText: String
|
||||
# Full query that was executed, contains all used filters
|
||||
"Full query that was executed, contains all used filters"
|
||||
fullQuery: String
|
||||
# Original query that was executed, without modifications
|
||||
"Original query that was executed, without modifications"
|
||||
originalQuery: String
|
||||
# Results
|
||||
"Results"
|
||||
results: [ SQLQueryResults! ]!
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertySourceCustom;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -209,9 +208,16 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
dataSourceContainer,
|
||||
cfg
|
||||
);
|
||||
Map<String, Object> connectionProperties = driver.getConnectionProperties();
|
||||
List<DBPPropertyDescriptor> propertyList = new ArrayList<>(Arrays.asList(properties));
|
||||
Set<String> propertyNames = propertyList.stream().map(DBPPropertyDescriptor::getId).collect(Collectors.toSet());
|
||||
Map<String, Object> connectionProperties = new LinkedHashMap<>(driver.getConnectionProperties());
|
||||
// In case of collision, value from connectionProperties will be used for a driver property.
|
||||
// Default value of property will be from driver.
|
||||
for (Map.Entry<String, Object> connProp : connectionProperties.entrySet()) {
|
||||
String propName = connProp.getKey();
|
||||
if (propertyNames.contains(propName)) {
|
||||
continue;
|
||||
}
|
||||
Object propValue = connProp.getValue();
|
||||
DBPPropertyDescriptor dbpPropertyDescriptor = new PropertyDescriptor(
|
||||
null,
|
||||
@@ -223,19 +229,20 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
propValue,
|
||||
null
|
||||
);
|
||||
properties = ArrayUtils.add(DBPPropertyDescriptor.class, properties, dbpPropertyDescriptor);
|
||||
propertyList.add(dbpPropertyDescriptor);
|
||||
cfg.setProperty(propName, (String) propValue);
|
||||
}
|
||||
if (properties == null) {
|
||||
if (propertyList.isEmpty()) {
|
||||
return new WebPropertyInfo[0];
|
||||
}
|
||||
connectionProperties.putAll(cfg.getProperties());
|
||||
|
||||
PropertySourceCustom propertySource = new PropertySourceCustom(
|
||||
properties,
|
||||
cfg.getProperties()
|
||||
propertyList,
|
||||
connectionProperties
|
||||
);
|
||||
|
||||
return Arrays.stream(properties)
|
||||
return propertyList.stream()
|
||||
.map(p -> new WebPropertyInfo(webSession, p, propertySource)).toArray(WebPropertyInfo[]::new);
|
||||
} catch (DBException e) {
|
||||
log.error("Error reading driver properties:\n" + e.getMessage());
|
||||
|
||||
+2
-5
@@ -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,7 +20,6 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.WorkspaceConfigEventManager;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
|
||||
import org.jkiss.dbeaver.registry.driver.DriverDescriptorSerializerLegacy;
|
||||
|
||||
public class WSEventHandlerWorkspaceConfigUpdate extends WSDefaultEventHandler<WSWorkspaceConfigurationChangedEvent> {
|
||||
private static final Log log = Log.getLog(WSEventHandlerWorkspaceConfigUpdate.class);
|
||||
@@ -30,8 +29,6 @@ public class WSEventHandlerWorkspaceConfigUpdate extends WSDefaultEventHandler<W
|
||||
String configFileName = event.getConfigFilePath();
|
||||
log.info("Config file changed: " + configFileName);
|
||||
WorkspaceConfigEventManager.fireConfigChangedEvent(configFileName);
|
||||
if (DriverDescriptorSerializerLegacy.DRIVERS_FILE_NAME.equals(event.getConfigFilePath())) {
|
||||
super.handleEvent(event);
|
||||
}
|
||||
super.handleEvent(event);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class WSServerConfigurationChangedEventHandler extends WSDefaultEventHand
|
||||
|
||||
@Override
|
||||
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSServerConfigurationChangedEvent event) {
|
||||
activeUserSession.refreshUserData();
|
||||
activeUserSession.refreshUserPermissions();
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -168,7 +168,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
log.debug("LP request rejected: access token expired");
|
||||
sendError(resp, HttpConstants.CODE_TOKEN_EXPIRED, e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
log.warn("LP session resolve failed", e);
|
||||
log.debug("LP request rejected: " + e.getMessage());
|
||||
sendError(resp, HttpConstants.CODE_UNAUTHORIZED, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
@@ -215,6 +215,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
|
||||
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER, sid);
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER_LEGACY, sid);
|
||||
log.debug("HTTP Long-Poll channel opened for session " + sid);
|
||||
|
||||
return ps;
|
||||
@@ -304,7 +305,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
@Override
|
||||
public BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException {
|
||||
|
||||
String sid = req.getHeader(WSConstants.WS_SESSION_HEADER);
|
||||
String sid = ServletAppUtils.getHeaderOrLegacy(req, WSConstants.WS_SESSION_HEADER, WSConstants.WS_SESSION_HEADER_LEGACY);
|
||||
String token = req.getHeader(WSConstants.WS_AUTH_HEADER);
|
||||
|
||||
if (CommonUtils.isEmpty(sid) && CommonUtils.isEmpty(token)) {
|
||||
|
||||
+6
-1
@@ -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.
|
||||
@@ -47,6 +47,11 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
|
||||
} else {
|
||||
this.webSession = (BaseWebSession) session.getUserProperties()
|
||||
.get(CBWebSocketServerConfigurator.PROP_WEB_SESSION);
|
||||
if (this.webSession == null) {
|
||||
log.debug("No web session for websocket connection, closing");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
this.webSession.addEventHandler(this);
|
||||
handleEvent(new WSSocketConnectedEvent(webSession.getApplication().getApplicationRunId()));
|
||||
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
|
||||
|
||||
+3
-2
@@ -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.
|
||||
@@ -88,7 +88,8 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
if (sec.getUserProperties().get(PROP_WEB_SESSION) == null) {
|
||||
throw new RuntimeException("No web session found for websocket request");
|
||||
// no throwing exception here. will be handled in onOpen
|
||||
log.debug("No web session found for websocket request, connection will be closed on open");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -461,6 +461,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
return node.getNodeUri();
|
||||
}
|
||||
if (node instanceof DBNDatabaseNode dbNode) {
|
||||
checkMetadataEditPermission(dbNode);
|
||||
return renameDatabaseObject(
|
||||
session,
|
||||
dbNode,
|
||||
@@ -545,8 +546,9 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
throw new DBWebException("Navigator node '" + path + "' not found");
|
||||
}
|
||||
checkProjectEditAccess(node, session);
|
||||
if (node instanceof DBNDatabaseNode) {
|
||||
DBSObject object = ((DBNDatabaseNode) node).getObject();
|
||||
if (node instanceof DBNDatabaseNode dbnDatabaseNode) {
|
||||
checkMetadataEditPermission(dbnDatabaseNode);
|
||||
DBSObject object = dbnDatabaseNode.getObject();
|
||||
DBEObjectMaker objectDeleter = DBWorkbench.getPlatform().getEditorsRegistry().getObjectManager(
|
||||
object.getClass(), DBEObjectMaker.class);
|
||||
if (objectDeleter == null || !objectDeleter.canDeleteObject(object)) {
|
||||
@@ -602,6 +604,12 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMetadataEditPermission(@NotNull DBNDatabaseNode node) throws DBException {
|
||||
if (!node.getDataSourceContainer().hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) {
|
||||
throw new DBWebException("Structure edit is restricted for this connection");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkProjectEditAccess(@NotNull DBNNode node, @NotNull WebSession session) throws DBException {
|
||||
var project = node.getOwnerProject();
|
||||
if (!(project instanceof BaseWebProjectImpl bwp) || !hasNodeEditPermission(session, node, bwp.getRMProject())) {
|
||||
|
||||
+11
-3
@@ -22,6 +22,7 @@ import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
|
||||
import org.jkiss.dbeaver.model.data.DBDDataFilter;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -113,25 +114,32 @@ public class WebSQLDataFilter {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DBDDataFilter makeDataFilter(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
|
||||
public DBDDataFilter makeDataFilter(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@Nullable WebSQLResultsInfo resultInfo
|
||||
) throws DBException {
|
||||
DBDDataFilter dataFilter = new DBDDataFilter();
|
||||
dataFilter.setWhere(where);
|
||||
dataFilter.setAnyConstraint(anyConstraint);
|
||||
if (CommonUtils.isEmpty(constraints)) {
|
||||
return dataFilter;
|
||||
}
|
||||
dataFilter.addConstraints(mapWebConstrainsToDbdConstrains(resultInfo));
|
||||
dataFilter.addConstraints(mapWebConstrainsToDbdConstrains(monitor, resultInfo));
|
||||
return dataFilter;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private List<DBDAttributeConstraint> mapWebConstrainsToDbdConstrains(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
|
||||
private List<DBDAttributeConstraint> mapWebConstrainsToDbdConstrains(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@Nullable WebSQLResultsInfo resultInfo
|
||||
) throws DBException {
|
||||
if (resultInfo == null) {
|
||||
return getDbdConstraints();
|
||||
}
|
||||
List<DBDAttributeConstraint> constraints = generateEmptyConstrains(resultInfo);
|
||||
fillEmptyConstrains(constraints);
|
||||
WebSQLUtils.convertConstraintValues(monitor, resultInfo, constraints);
|
||||
return constraints;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -195,7 +195,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBCExecutionContext context = getExecutionContext(dataContainer);
|
||||
|
||||
try {
|
||||
final DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
final DBDDataFilter dataFilter = filter.makeDataFilter(
|
||||
monitor, (resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
if (dataFilter.hasFilters()) {
|
||||
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
|
||||
monitor,
|
||||
@@ -346,7 +347,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo();
|
||||
|
||||
DBCExecutionContext executionContext = DBUtils.getOrOpenDefaultContext(dataContainer, false);
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter(
|
||||
monitor, (resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
|
||||
try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) {
|
||||
try (
|
||||
|
||||
@@ -29,8 +29,11 @@ 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.data.*;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
|
||||
import org.jkiss.dbeaver.model.exec.DBCSession;
|
||||
import org.jkiss.dbeaver.model.gis.DBGeometry;
|
||||
import org.jkiss.dbeaver.model.gis.GisConstants;
|
||||
@@ -266,6 +269,49 @@ public class WebSQLUtils {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Need to convert string values in constraints to real values using value handlers
|
||||
*/
|
||||
public static void convertConstraintValues(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLResultsInfo resultInfo,
|
||||
@NotNull List<DBDAttributeConstraint> constraints
|
||||
) {
|
||||
DBCExecutionContext executionContext = DBUtils.getDefaultContext(resultInfo.getDataContainer(), false);
|
||||
if (executionContext == null || !executionContext.isConnected()) {
|
||||
return;
|
||||
}
|
||||
DBCSession session = null;
|
||||
try {
|
||||
for (DBDAttributeConstraint constraint : constraints) {
|
||||
if (!(constraint.getValue() instanceof String strValue) ||
|
||||
!(constraint.getAttribute() instanceof DBDAttributeBinding binding)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (session == null) {
|
||||
session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL, "Convert filter values");
|
||||
}
|
||||
try {
|
||||
Object value = binding.getValueHandler().getValueFromObject(session, binding, strValue, false, false);
|
||||
if (value != null) {
|
||||
constraint.setValue(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"Can't convert filter value '" + strValue + "' for attribute '"
|
||||
+ binding.getName() + "'", e
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fully qualified name for a column.
|
||||
*/
|
||||
|
||||
+14
-4
@@ -31,10 +31,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataKind;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.*;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
@@ -509,6 +506,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@Nullable List<WebSQLResultsRow> addedRows,
|
||||
@Nullable WebDataFormat dataFormat
|
||||
) throws DBException {
|
||||
checkDataEditPermission(contextInfo);
|
||||
WebSQLExecuteInfo[] result = new WebSQLExecuteInfo[1];
|
||||
|
||||
DBExecUtils.tryExecuteRecover(
|
||||
@@ -521,6 +519,13 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private void checkDataEditPermission(@NotNull WebSQLContextInfo contextInfo) throws DBWebException {
|
||||
if (!contextInfo.getProcessor().getConnection().getDataSourceContainer()
|
||||
.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) {
|
||||
throw new DBWebException("Data edit is restricted for this connection");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowableFunction<T, R> {
|
||||
R apply(T obj) throws DBException;
|
||||
@@ -573,6 +578,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
|
||||
@Override
|
||||
public String updateResultsDataBatchScript(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @Nullable List<WebSQLResultsRow> updatedRows, @Nullable List<WebSQLResultsRow> deletedRows, @Nullable List<WebSQLResultsRow> addedRows, WebDataFormat dataFormat) throws DBWebException {
|
||||
checkDataEditPermission(contextInfo);
|
||||
try {
|
||||
return contextInfo.getProcessor().generateResultsDataUpdateScript(
|
||||
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
|
||||
@@ -599,6 +605,10 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
if (DBWorkbench.isDistributed() && !webSession.hasPermission(DBWConstants.PERMISSION_SQL_EXECUTE_QUERY)) {
|
||||
throw new DBWebException("Permission denied");
|
||||
}
|
||||
if (!contextInfo.getProcessor().getConnection().getDataSourceContainer()
|
||||
.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) {
|
||||
throw new DBWebException("Script execution is restricted for this connection");
|
||||
}
|
||||
return WebSQLUtils.createAsyncTaskExecuteSqlQuery(
|
||||
webSession,
|
||||
contextInfo,
|
||||
|
||||
@@ -3,10 +3,9 @@ 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.151.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.153.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
org.jkiss.dbeaver.registry
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
Automatic-Module-Name: io.cloudbeaver.service.admin
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.admin</artifactId>
|
||||
<version>1.0.151-SNAPSHOT</version>
|
||||
<version>1.0.153-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Administration API
|
||||
####################################################
|
||||
|
||||
"Describes the type of the administrative subject (user or team)"
|
||||
enum AdminSubjectType {
|
||||
"User"
|
||||
|
||||
+64
-10
@@ -293,11 +293,6 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
if (grantor == null) {
|
||||
throw new DBWebException("Cannot grant team in anonymous mode");
|
||||
}
|
||||
if (!ServletAppUtils.getServletApplication().isDistributed()
|
||||
&& CommonUtils.equalObjects(user, webSession.getUser().getUserId())
|
||||
) {
|
||||
throw new DBWebException("You cannot edit your own permissions");
|
||||
}
|
||||
try {
|
||||
var adminSecurityController = webSession.getAdminSecurityController();
|
||||
adminSecurityController.addUserTeams(user, new String[]{team}, grantor.getUserId());
|
||||
@@ -314,10 +309,8 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
if (grantor == null) {
|
||||
throw new DBWebException("Cannot revoke team in anonymous mode");
|
||||
}
|
||||
if (!ServletAppUtils.getServletApplication().isDistributed() &&
|
||||
CommonUtils.equalObjects(user, webSession.getUser().getUserId())
|
||||
) {
|
||||
throw new DBWebException("You cannot edit your own permissions");
|
||||
if (CommonUtils.equalObjects(user, grantor.getUserId())) {
|
||||
checkAdminPermissionsRetained(webSession, user, team);
|
||||
}
|
||||
try {
|
||||
var adminSecurityController = webSession.getAdminSecurityController();
|
||||
@@ -345,6 +338,11 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
if (CommonUtils.equalObjects(subjectID, CBConstants.DEFAULT_ADMIN_TEAM)) {
|
||||
throw new DBWebException("Cannot change permissions for team '" + subjectID + "'");
|
||||
}
|
||||
if (!permissions.contains(DBWConstants.PERMISSION_ADMIN)
|
||||
&& isOwnSubject(webSession, grantor.getUserId(), subjectID)
|
||||
) {
|
||||
checkAdminPermissionsRetained(webSession, grantor.getUserId(), subjectID);
|
||||
}
|
||||
webSession.addInfoMessage("Set permissions to subject - " + subjectID);
|
||||
|
||||
try {
|
||||
@@ -359,6 +357,62 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified subject is the user himself or one of his teams.
|
||||
*/
|
||||
private boolean isOwnSubject(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String userId,
|
||||
@NotNull String subjectId
|
||||
) throws DBWebException {
|
||||
if (subjectId.equals(userId)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
for (SMTeam userTeam : webSession.getAdminSecurityController().getUserTeams(userId)) {
|
||||
if (userTeam.getTeamId().equals(subjectId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error reading teams of user " + userId, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the user still has administrative permission after revoking permissions from the specified subject.
|
||||
* Prevents the last administrator from locking himself out of the server.
|
||||
*/
|
||||
private void checkAdminPermissionsRetained(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String userId,
|
||||
@NotNull String revokedSubjectId
|
||||
) throws DBWebException {
|
||||
if (ServletAppUtils.getServletApplication().isDistributed()) {
|
||||
// permissions are granted by auth roles, not by subjects
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var adminSecurityController = webSession.getAdminSecurityController();
|
||||
if (!adminSecurityController.getSubjectPermissions(revokedSubjectId).contains(DBWConstants.PERMISSION_ADMIN)) {
|
||||
// subject doesn't grant administrative permission, nothing to lose
|
||||
return;
|
||||
}
|
||||
for (SMTeam userTeam : adminSecurityController.getUserTeams(userId)) {
|
||||
String teamId = userTeam.getTeamId();
|
||||
if (!teamId.equals(revokedSubjectId)
|
||||
&& adminSecurityController.getSubjectPermissions(teamId).contains(DBWConstants.PERMISSION_ADMIN)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error reading permissions of user " + userId, e);
|
||||
}
|
||||
throw new DBWebException("You cannot revoke your own administrative permissions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setUserCredentials(@NotNull WebSession webSession,
|
||||
@NotNull String userID,
|
||||
@@ -645,7 +699,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
webSession.resetUserState();
|
||||
} else {
|
||||
// Just reload session state
|
||||
webSession.refreshUserData();
|
||||
webSession.refreshUserPermissions();
|
||||
}
|
||||
|
||||
WebAppUtils.getWebApplication().getDriverRegistry().refreshApplicableDrivers();
|
||||
|
||||
@@ -2,14 +2,13 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Cloudbeaver AI Integration
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.ai;singleton:=true
|
||||
Bundle-Version: 1.0.1.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.3.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.service.ai
|
||||
Require-Bundle: org.jkiss.dbeaver.model.ai,
|
||||
io.cloudbeaver.model,
|
||||
io.cloudbeaver.server.ce
|
||||
Require-Bundle: org.jkiss.dbeaver.model.ai;visibility:=reexport,
|
||||
io.cloudbeaver.server.ce;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver.service.ai,
|
||||
io.cloudbeaver.service.ai.model,
|
||||
io.cloudbeaver.service.ai.model.inputs
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.ai</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.0.3-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# AI events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
cb_ai_chat_message_chunk @since(version: "25.1.1")
|
||||
cb_ai_chat_message_error @since(version: "25.1.1")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
####################################################
|
||||
# AI admin queries
|
||||
####################################################
|
||||
|
||||
enum AIMessageType {
|
||||
SYSTEM,
|
||||
USER,
|
||||
@@ -32,6 +35,19 @@ type AIEngineInfo @since(version: "23.2.2") {
|
||||
id: ID!
|
||||
"Name of the AI engine."
|
||||
name: String!
|
||||
"Path to the AI engine icon."
|
||||
icon: String
|
||||
}
|
||||
|
||||
type AIModelInfo @since(version: "26.2.0") {
|
||||
"Unique model identifier used by the AI provider."
|
||||
id: ID!
|
||||
"Maximum model context window size, if reported by the provider."
|
||||
contextWindowSize: Int
|
||||
"Default model temperature."
|
||||
defaultTemperature: Float!
|
||||
"Capabilities reported for the model."
|
||||
features: [String!]!
|
||||
}
|
||||
|
||||
type AIChatConversationInfo @since(version: "25.1.1") {
|
||||
@@ -217,6 +233,8 @@ extend type Query @since(version: "23.2.2") {
|
||||
aiSettings: AISettingsInfo!
|
||||
"Returns the properties of the specified AI engine for displaying it in the UI. In case settings are passed, fills it with defaults and returns back."
|
||||
aiListEngineProperties(engineId: ID!, profileId: ID, settings : AIEngineConfig): [ObjectPropertyInfo!]!
|
||||
"Returns models available for the specified AI engine configuration."
|
||||
aiListEngineModels(engineId: ID!, profileId: ID, settings: AIEngineConfig): [AIModelInfo!]! @since(version: "26.2.0")
|
||||
|
||||
"Return the list of available AI functions."
|
||||
aiListFunctions: [AIFunction!] @since(version: "26.0.4")
|
||||
@@ -269,6 +287,9 @@ extend type Mutation @since(version: "23.2.2") {
|
||||
"Clears the chat messages in the AI chat conversation. The messages will be removed from the conversation."
|
||||
aiClearLastChatMessages(conversationId: ID!, messageId: ID!): Boolean! @since(version: "25.1.1")
|
||||
|
||||
"Cancels the in-progress AI response generation in the specified conversation."
|
||||
aiCancelChatMessage(conversationId: ID!): Boolean! @since(version: "26.1.5")
|
||||
|
||||
"Saves AI settings (e.g. supporting confirming metadata transfer) for the specified connection."
|
||||
aiSaveDataSourceSettings(dataSourceId: DataSourceIdInput!, settings: AIDataSourceSettingsInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
|
||||
}
|
||||
|
||||
+41
-24
@@ -24,6 +24,8 @@ import io.cloudbeaver.service.ai.model.WebAISendChatMessageInfo;
|
||||
import io.cloudbeaver.service.ai.model.WebAiChatResponseConsumer;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -39,9 +41,9 @@ import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNUtils;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObject;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.time.Clock;
|
||||
@@ -139,10 +141,11 @@ public class WebAIUtils {
|
||||
@Nullable AIConfirmation confirmation,
|
||||
@NotNull String jobName
|
||||
) {
|
||||
webSession.setAttribute(getWaitingAttr(conversation), true);
|
||||
CompletableFuture<AIChatConversation> result = new CompletableFuture<>();
|
||||
RuntimeUtils.scheduleJob(
|
||||
jobName, monitor -> {
|
||||
AbstractJob job = new AbstractJob(jobName) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
|
||||
try {
|
||||
AIChatResponseConsumer subscriber = new WebAiChatResponseConsumer(conversation, webSession, aiChatSession);
|
||||
aiChatSession.processAICompletion(
|
||||
@@ -159,15 +162,25 @@ public class WebAIUtils {
|
||||
}
|
||||
});
|
||||
} catch (DBException e) {
|
||||
log.error("Error processing AI completion", e);
|
||||
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, errorMessage);
|
||||
if (monitor.isCanceled()) {
|
||||
log.debug("AI completion cancelled", e);
|
||||
} else {
|
||||
log.error("Error processing AI completion", e);
|
||||
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, errorMessage);
|
||||
}
|
||||
} finally {
|
||||
webSession.removeAttribute(getWaitingAttr(conversation));
|
||||
// Only clear the flag if it still points to this job
|
||||
if (webSession.getAttribute(getWaitingAttr(conversation)) == this) {
|
||||
webSession.removeAttribute(getWaitingAttr(conversation));
|
||||
}
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
);
|
||||
};
|
||||
webSession.setAttribute(getWaitingAttr(conversation), job);
|
||||
job.schedule();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -257,21 +270,25 @@ public class WebAIUtils {
|
||||
throw new DBWebException("AI services restricted for '%s'. Please contact your administrator if you need it.".formatted(
|
||||
conversation.getDataSource()));
|
||||
}
|
||||
String caption = conversation.getCaption();
|
||||
AIChatMessage promptMessage = conversation.addMessage(message);
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, promptMessage);
|
||||
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
|
||||
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
|
||||
AIChatMessage promptMessage;
|
||||
AIChatMessage result;
|
||||
synchronized (conversation) {
|
||||
String caption = conversation.getCaption();
|
||||
promptMessage = conversation.addMessage(message);
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, promptMessage);
|
||||
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
|
||||
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
|
||||
}
|
||||
if (!AIUtils.hasValidConfiguration()) {
|
||||
throw new DBWebException("Invalid AI configuration");
|
||||
}
|
||||
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
|
||||
throw new DBWebException("Conversation is already waiting for response");
|
||||
}
|
||||
result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
|
||||
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
|
||||
}
|
||||
if (!AIUtils.hasValidConfiguration()) {
|
||||
throw new DBWebException("Invalid AI configuration");
|
||||
}
|
||||
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
|
||||
throw new DBWebException("Conversation is already waiting for response");
|
||||
}
|
||||
AIChatMessage result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
|
||||
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
|
||||
return new WebAISendChatMessageInfo(
|
||||
new WebAIChatConversation(webSession, conversation),
|
||||
new WebAIMessage(promptMessage, conversation),
|
||||
|
||||
+17
-2
@@ -45,7 +45,7 @@ public interface DBWServiceAI extends DBWService {
|
||||
WebAISettingsConfig getAiSettings() throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
@WebAction
|
||||
List<WebAIEngine> getEngineConfigurations() throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@@ -54,7 +54,16 @@ public interface DBWServiceAI extends DBWService {
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String engineId,
|
||||
@Nullable String profileId,
|
||||
@Nullable Map<String, Object> engineSettings
|
||||
@WebParameterSecure @Nullable Map<String, Object> engineSettings
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
List<WebAIModel> getEngineModels(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String engineId,
|
||||
@Nullable String profileId,
|
||||
@WebParameterSecure @Nullable Map<String, Object> engineSettings
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@@ -139,6 +148,12 @@ public interface DBWServiceAI extends DBWService {
|
||||
@NotNull String messageId
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
boolean cancelChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAIDataSourceSettings getDataSourceAiSettings(
|
||||
|
||||
+8
@@ -16,7 +16,9 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.registry.AIEngineDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
@@ -41,4 +43,10 @@ public class WebAIEngine {
|
||||
return descriptor.getLabel();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getIcon() {
|
||||
return WebServiceUtils.makeIconId(descriptor.getIcon());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+88
-11
@@ -25,6 +25,7 @@ import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.service.ai.WebAIUtils;
|
||||
import io.cloudbeaver.service.ai.model.*;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
|
||||
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
|
||||
@@ -46,12 +47,14 @@ import org.jkiss.dbeaver.model.ai.engine.AIDatabaseContext;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIEngine;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIEngineProperties;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIModel;
|
||||
import org.jkiss.dbeaver.model.ai.internal.AIChatMessages;
|
||||
import org.jkiss.dbeaver.model.ai.prompt.AIPromptGenerateSql;
|
||||
import org.jkiss.dbeaver.model.ai.registry.*;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.logical.DBSLogicalDataSource;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
@@ -72,16 +75,12 @@ public class WebServiceAI implements DBWServiceAI {
|
||||
public WebAISettingsConfig getAiSettings() throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
try {
|
||||
AIConfigurationProfile configuration = settings.getDefaultConfiguration();
|
||||
return new WebAISettingsConfig(
|
||||
configuration.getEngineId(),
|
||||
configuration.getProfileId(),
|
||||
getAiLanguage()
|
||||
);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e);
|
||||
}
|
||||
AIConfigurationProfile configuration = settings.getDefaultConfigurationOrNull();
|
||||
return new WebAISettingsConfig(
|
||||
configuration == null ? "" : configuration.getEngineId(),
|
||||
configuration == null ? null : configuration.getProfileId(),
|
||||
getAiLanguage()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -105,7 +104,10 @@ public class WebServiceAI implements DBWServiceAI {
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AIConfigurationProfile profile = getDefaultConfiguration(engineId, profileId);
|
||||
AIConfigurationProfile profile = copyConfigurationProfile(
|
||||
webSession.getProgressMonitor(),
|
||||
getDefaultConfiguration(engineId, profileId)
|
||||
);
|
||||
|
||||
AIEngineProperties engineConfiguration;
|
||||
if (settingsInput != null) {
|
||||
@@ -126,6 +128,52 @@ public class WebServiceAI implements DBWServiceAI {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebAIModel> getEngineModels(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String engineId,
|
||||
@Nullable String profileId,
|
||||
@Nullable Map<String, Object> settingsInput
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AIConfigurationProfile profile = copyConfigurationProfile(
|
||||
webSession.getProgressMonitor(),
|
||||
getDefaultConfiguration(engineId, profileId)
|
||||
);
|
||||
AIEngineProperties configuration = settingsInput == null
|
||||
? profile.getConfiguration()
|
||||
: toEngineConfiguration(webSession.getProgressMonitor(), profile, settingsInput);
|
||||
|
||||
try (AIEngine<?> engine = profile.getEngineDescriptor().createEngineInstance(configuration)) {
|
||||
return engine.getModels(webSession.getProgressMonitor()).stream().map(WebAIModel::new).toList();
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error getting AI engine models", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AIConfigurationProfile copyConfigurationProfile(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull AIConfigurationProfile sourceProfile
|
||||
) throws DBException {
|
||||
AIEngineProperties source = sourceProfile.getConfiguration();
|
||||
PropertySourceEditable sourceProperties = new PropertySourceEditable(source, source);
|
||||
sourceProperties.collectProperties();
|
||||
|
||||
Map<String, Object> sourceValues = new HashMap<>();
|
||||
for (DBPPropertyDescriptor property : sourceProperties.getProperties()) {
|
||||
sourceValues.put(property.getId(), sourceProperties.getPropertyValue(monitor, property.getId()));
|
||||
}
|
||||
|
||||
AIConfigurationProfile targetProfile = new AIConfigurationProfile();
|
||||
targetProfile.setEngineId(sourceProfile.getEngineId());
|
||||
targetProfile.setConfiguration(toEngineConfiguration(monitor, targetProfile, Map.of("properties", sourceValues)));
|
||||
return targetProfile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AIConfigurationProfile getDefaultConfiguration(@NotNull String engineId, @Nullable String profileId) throws DBException {
|
||||
AISettings aiSettings = AISettingsManager.getInstance().getSettings();
|
||||
@@ -397,6 +445,33 @@ public class WebServiceAI implements DBWServiceAI {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
|
||||
synchronized (conversation) {
|
||||
boolean completionStarted = conversation.isActive();
|
||||
conversation.cancelConversation();
|
||||
boolean hadPendingJob = false;
|
||||
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) instanceof AbstractJob job) {
|
||||
hadPendingJob = true;
|
||||
job.cancel();
|
||||
}
|
||||
webSession.removeAttribute(WebAIUtils.getWaitingAttr(conversation));
|
||||
if (hadPendingJob && !completionStarted) {
|
||||
// The completion job was still queued, so its response consumer will not be called.
|
||||
// We need to add a cancellation message to the conversation and notify the client.
|
||||
AIChatMessage cancelMessage = conversation.addMessage(
|
||||
AIMessage.warningMessage(AIChatMessages.ai_chat_conversation_cancelled));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(cancelMessage, conversation)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIDataSourceSettings getDataSourceAiSettings(
|
||||
@@ -512,6 +587,8 @@ public class WebServiceAI implements DBWServiceAI {
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
AIConfigurationProfile profile = settings.getConfiguration(profileId);
|
||||
settings.removeConfiguration(profile);
|
||||
AISettingsManager.getInstance().saveSettings();
|
||||
addAISettingsChangedEvent(webSession);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error deleting AI configuration " + profileId, e);
|
||||
}
|
||||
|
||||
+14
@@ -62,6 +62,14 @@ public class WebServiceBindingAI extends WebServiceBindingBase<DBWServiceAI> imp
|
||||
getArgument(env, "profileId"),
|
||||
env.getArgument("settings")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiListEngineModels",
|
||||
env -> getService(env).getEngineModels(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "engineId"),
|
||||
getArgument(env, "profileId"),
|
||||
env.getArgument("settings")
|
||||
)
|
||||
).dataFetcher("aiListFunctions", env -> getService(env).getFunctions(getWebSession(env))
|
||||
).dataFetcher("aiListProfiles", env -> getService(env).getProfiles(getWebSession(env))
|
||||
).dataFetcher(
|
||||
@@ -163,6 +171,12 @@ public class WebServiceBindingAI extends WebServiceBindingBase<DBWServiceAI> imp
|
||||
getArgumentVal(env, "conversationId"),
|
||||
getArgumentVal(env, "messageId")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiCancelChatMessage",
|
||||
env -> getService(env).cancelChatMessage(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiCreateProfile", env -> getService(env).createProfile(
|
||||
getWebSession(env),
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.ai.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIModel;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIModelFeature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record WebAIModel(
|
||||
@NotNull String id,
|
||||
@Nullable Integer contextWindowSize,
|
||||
double defaultTemperature,
|
||||
@NotNull List<String> features
|
||||
) {
|
||||
public WebAIModel(@NotNull AIModel model) {
|
||||
this(
|
||||
model.name(),
|
||||
model.contextWindowSize(),
|
||||
model.defaultTemperature(),
|
||||
model.features().stream().map(AIModelFeature::name).toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
+27
-13
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* 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
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
* 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.ai.model;
|
||||
|
||||
@@ -22,9 +22,11 @@ import io.cloudbeaver.service.ai.model.events.WSAiChatMessageErrorEvent;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
import org.jkiss.dbeaver.model.ai.internal.AIChatMessages;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
|
||||
private final StringBuilder responseBuilder;
|
||||
@@ -78,7 +80,12 @@ public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
|
||||
|
||||
@Override
|
||||
public void error(@NotNull Throwable throwable) {
|
||||
var errorMessage = conversation.addMessage(AIMessage.errorMessage(throwable));
|
||||
|
||||
AIMessage aiMessage = throwable instanceof CancellationException cancellationException
|
||||
? AIMessage.warningMessage(cancellationException.getMessage())
|
||||
: AIMessage.errorMessage(throwable);
|
||||
|
||||
AIChatMessage errorMessage = conversation.addMessage(aiMessage);
|
||||
if (responseBuilder.isEmpty()) {
|
||||
webSession.addSessionEvent(
|
||||
new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
|
||||
@@ -94,12 +101,19 @@ public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation) {
|
||||
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation, boolean isCanceled) {
|
||||
if (responseBuilder.isEmpty()) {
|
||||
if (isCanceled) {
|
||||
warning(AIChatMessages.ai_chat_conversation_cancelled);
|
||||
}
|
||||
return;
|
||||
}
|
||||
AIChatMessage responseMessage = conversation.addMessage(AIMessage.assistantMessage(responseBuilder.toString(), meta));
|
||||
chatSession.notifyMessageAdd(conversation, responseMessage);
|
||||
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(conversation.getId(), responseMessage.id(), null, true));
|
||||
|
||||
if (isCanceled) {
|
||||
warning(AIChatMessages.ai_chat_conversation_cancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.151.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.153.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.151-SNAPSHOT</version>
|
||||
<version>1.0.153-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Authentication API
|
||||
####################################################
|
||||
|
||||
enum AuthCredentialEncryption {
|
||||
none,
|
||||
plain,
|
||||
@@ -15,11 +19,11 @@ type AuthCredentialInfo {
|
||||
displayName: String!
|
||||
description: String
|
||||
|
||||
# This field must be shown in admin panel
|
||||
"This field must be shown in admin panel"
|
||||
admin: Boolean!
|
||||
# This field must be shown in login form
|
||||
"This field must be shown in login form"
|
||||
user: Boolean!
|
||||
# This field can be used as user identifier
|
||||
"This field can be used as user identifier"
|
||||
|
||||
identifying: Boolean!
|
||||
possibleValues: [String]
|
||||
@@ -104,8 +108,14 @@ type FederatedAuthResult @since(version: "25.0.3") {
|
||||
userTokens: [UserAuthToken!]! @since(version: "25.0.3")
|
||||
}
|
||||
|
||||
type LogoutLink @since(version: "26.1.4") {
|
||||
url: String!
|
||||
"If true, the URL must be opened as a full-page redirect in the current tab instead of a popup window"
|
||||
sameTabRedirect: Boolean!
|
||||
}
|
||||
|
||||
type LogoutInfo @since(version: "23.3.3") {
|
||||
redirectLinks: [String!]!
|
||||
redirectLinks: [LogoutLink!]!
|
||||
}
|
||||
|
||||
type UserAuthToken {
|
||||
|
||||
+1
-1
@@ -20,5 +20,5 @@ import org.jkiss.code.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record WebLogoutInfo(@NotNull List<String> redirectLinks) {
|
||||
public record WebLogoutInfo(@NotNull List<WebLogoutLink> redirectLinks) {
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.auth;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
/**
|
||||
* A single logout redirect link.
|
||||
*
|
||||
* @param url the URL to open on logout
|
||||
* @param sameTabRedirect if true, the URL must be opened as a full-page redirect
|
||||
* in the current tab instead of a popup window
|
||||
*/
|
||||
public record WebLogoutLink(@NotNull String url, boolean sameTabRedirect) {
|
||||
}
|
||||
+6
-3
@@ -249,7 +249,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
List<WebAuthInfo> removedInfos = webSession.removeAuthInfo(providerId);
|
||||
var cbApp = CBApplication.getInstance();
|
||||
|
||||
List<String> logoutUrls = new ArrayList<>();
|
||||
List<WebLogoutLink> redirectLinks = new ArrayList<>();
|
||||
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
|
||||
for (WebAuthInfo removedInfo : removedInfos) {
|
||||
if (removedInfo.getAuthProviderDescriptor()
|
||||
@@ -274,11 +274,14 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
);
|
||||
}
|
||||
if (CommonUtils.isNotEmpty(logoutUrl)) {
|
||||
logoutUrls.add(logoutUrl);
|
||||
redirectLinks.add(new WebLogoutLink(
|
||||
logoutUrl,
|
||||
removedInfo.getAuthProviderDescriptor().isSameTabRedirectOnLogout()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new WebLogoutInfo(logoutUrls);
|
||||
return new WebLogoutInfo(redirectLinks);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("User logout failed", e);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ 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.152.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.154.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
org.jkiss.dbeaver.data.transfer,
|
||||
org.jkiss.dbeaver.registry
|
||||
org.jkiss.dbeaver.data.transfer
|
||||
Automatic-Module-Name: io.cloudbeaver.service.data.transfer
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
|
||||
<version>1.0.152-SNAPSHOT</version>
|
||||
<version>1.0.154-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+4
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Data transfer queries
|
||||
####################################################
|
||||
|
||||
type DataTransferProcessorInfo {
|
||||
id: ID!
|
||||
@@ -46,6 +48,8 @@ input DataTransferImportSettings {
|
||||
useBulkLoad: Boolean
|
||||
"Use transactions during import"
|
||||
useTransactions: Boolean
|
||||
"Open new connection(s) for the import task"
|
||||
openNewConnection: Boolean
|
||||
}
|
||||
|
||||
input DataTransferImportParameters {
|
||||
|
||||
+10
-2
@@ -32,6 +32,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourcePermission;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
@@ -245,6 +246,10 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
if (!validateImportPermission(webSession)) {
|
||||
throw new DBWebException("Permission denied. Data import is not allowed for this user");
|
||||
}
|
||||
if (!sqlContext.getProcessor().getConnection().getDataSourceContainer()
|
||||
.hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) {
|
||||
throw new DBWebException("Data import is restricted for this connection");
|
||||
}
|
||||
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(parameters.getProcessorId());
|
||||
if (processor == null) {
|
||||
throw new DBWebException("Wrong data processor '" + parameters.getProcessorId() + "'");
|
||||
@@ -351,7 +356,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
StreamConsumerSettings settings = makeStreamConsumerSettings(parameters);
|
||||
DatabaseTransferProducer producer = new DatabaseTransferProducer(
|
||||
dataContainer,
|
||||
parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter(resultsInfo));
|
||||
parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter(monitor, resultsInfo));
|
||||
|
||||
consumer.initTransfer(
|
||||
dataContainer,
|
||||
@@ -425,7 +430,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
throw new DBWebException("Import is canceled");
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Import failed cause: " + e.getMessage());
|
||||
throw new DBWebException("Import failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +451,9 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
consumerSettings.setUseTransactions(CommonUtils.getBoolean(
|
||||
settings.get(DTConstants.PROP_USE_TRANSACTIONS),
|
||||
consumerSettings.isUseTransactions()));
|
||||
consumerSettings.setOpenNewConnections(CommonUtils.getBoolean(
|
||||
settings.get(DTConstants.PROP_OPEN_NEW_CONNECTION),
|
||||
consumerSettings.isOpenNewConnections()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.69.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.71.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.69-SNAPSHOT</version>
|
||||
<version>1.0.71-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# File system API
|
||||
####################################################
|
||||
|
||||
type FSFile @since(version: "23.2.2") {
|
||||
"Name of the file or folder"
|
||||
name: String!
|
||||
|
||||
@@ -3,13 +3,11 @@ 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.34.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.36.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
org.jkiss.dbeaver.registry;visibility:=reexport,
|
||||
io.cloudbeaver.model
|
||||
Require-Bundle: io.cloudbeaver.model;visibility:=reexport
|
||||
Bundle-Localization: OSGI-INF/l10n/bundle
|
||||
Export-Package: io.cloudbeaver.service.ldap.auth,
|
||||
io.cloudbeaver.service.ldap.auth.ssl
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.ldap.auth</artifactId>
|
||||
<version>1.0.34-SNAPSHOT</version>
|
||||
<version>1.0.36-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+22
-6
@@ -48,6 +48,7 @@ import java.util.*;
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.PartialResultException;
|
||||
import javax.naming.directory.*;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
@@ -562,12 +563,22 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
}
|
||||
|
||||
context = initConnection(environment);
|
||||
List<String> groupsByMemberOfAttribute = findGroupsByMemberOfAttribute(fullDN, context);
|
||||
log.debug("Found " + groupsByMemberOfAttribute.size() + " groups by memberOf attribute");
|
||||
result.addAll(groupsByMemberOfAttribute);
|
||||
List<String> groupsByMemberAttribute = findGroupsByMemberAttribute(fullDN, ldapSettings, context);
|
||||
log.debug("Found " + groupsByMemberAttribute.size() + " groups by member attribute");
|
||||
result.addAll(groupsByMemberAttribute);
|
||||
// Run both lookups independently: a failure in one must not discard the groups
|
||||
// already collected by the other.
|
||||
try {
|
||||
List<String> groupsByMemberOfAttribute = findGroupsByMemberOfAttribute(fullDN, context);
|
||||
log.debug("Found " + groupsByMemberOfAttribute.size() + " groups by memberOf attribute");
|
||||
result.addAll(groupsByMemberOfAttribute);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to fetch groups by memberOf attribute. " + e.getMessage());
|
||||
}
|
||||
try {
|
||||
List<String> groupsByMemberAttribute = findGroupsByMemberAttribute(fullDN, ldapSettings, context);
|
||||
log.debug("Found " + groupsByMemberAttribute.size() + " groups by member attribute");
|
||||
result.addAll(groupsByMemberAttribute);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to fetch groups by member attribute. " + e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Group not found. " + e.getMessage());
|
||||
} finally {
|
||||
@@ -579,6 +590,7 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
log.error("Close resource of ldap group search failed", e);
|
||||
}
|
||||
}
|
||||
log.debug("Resolved " + result.size() + " external group id(s) for member '" + fullDN + "': " + result);
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
@@ -602,6 +614,8 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PartialResultException e) {
|
||||
log.debug("Ignoring LDAP continuation references while reading memberOf for '" + fullDN + "'");
|
||||
} finally {
|
||||
if (userRecord != null) {
|
||||
userRecord.close();
|
||||
@@ -634,6 +648,8 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
log.error("Failed fetch user group. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (PartialResultException e) {
|
||||
log.debug("Ignoring LDAP continuation references during member group search for '" + fullDN + "'");
|
||||
} finally {
|
||||
if (searchResults != null) {
|
||||
searchResults.close();
|
||||
|
||||
@@ -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.155.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.157.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.155-SNAPSHOT</version>
|
||||
<version>1.0.157-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Metadata queries
|
||||
####################################################
|
||||
|
||||
extend type Query {
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@ 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.69.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.71.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.model,
|
||||
org.jkiss.dbeaver.model,
|
||||
org.jkiss.dbeaver.model.nio
|
||||
Require-Bundle: io.cloudbeaver.model;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver.service.rm.nio,
|
||||
io.cloudbeaver.service.rm.fs
|
||||
Automatic-Module-Name: io.cloudbeaver.service.rm.nio
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
|
||||
<version>1.0.69-SNAPSHOT</version>
|
||||
<version>1.0.71-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.104.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.106.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.104-SNAPSHOT</version>
|
||||
<version>1.0.106-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Resource manager events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
"Resource manager resource created"
|
||||
cb_rm_resource_created,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Metadata queries
|
||||
####################################################
|
||||
|
||||
type RMProject {
|
||||
id: ID!
|
||||
@@ -17,7 +19,7 @@ type RMResource {
|
||||
name: String!
|
||||
folder: Boolean!
|
||||
length: Int!
|
||||
# Properties map
|
||||
"Properties map"
|
||||
properties: Object
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@ 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.107.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.109.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.jdbc,
|
||||
org.jkiss.dbeaver.model.sql,
|
||||
org.jkiss.dbeaver.model.sql.jdbc;visibility:=reexport,
|
||||
org.jkiss.dbeaver.registry;visibility:=reexport,
|
||||
Require-Bundle: org.jkiss.dbeaver.model.sql.jdbc;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp;visibility:=reexport,
|
||||
io.cloudbeaver.model
|
||||
io.cloudbeaver.model;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver.auth.provider.local,
|
||||
io.cloudbeaver.auth.provider.rp,
|
||||
io.cloudbeaver.service.security,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
description="Reverse proxy header based authentication"
|
||||
trusted="true"
|
||||
configurable="true"
|
||||
sameTabRedirectOnLogout="true"
|
||||
class="io.cloudbeaver.auth.provider.rp.RPAuthProvider"
|
||||
icon="platform:/plugin/org.jkiss.dbeaver.model/icons/tree/key.svg">
|
||||
<credentials>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.security</artifactId>
|
||||
<version>1.0.107-SNAPSHOT</version>
|
||||
<version>1.0.109-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+172
-68
@@ -81,6 +81,8 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
|
||||
protected static final String CHAR_BOOL_TRUE = "Y";
|
||||
protected static final String CHAR_BOOL_FALSE = "N";
|
||||
private static final String CRED_ID_PROVISIONED = "$provisioned";
|
||||
private static final String CRED_VALUE_PROVISIONED = "Y";
|
||||
|
||||
private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken<Map<String, Object>>() {
|
||||
}.getType();
|
||||
@@ -419,13 +421,8 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
List<SMUserProvisioning> users = userImportList.getUsers();
|
||||
List<SMUserProvisioning> importedUsers = new ArrayList<>();
|
||||
Set<String> seen = new HashSet<>();
|
||||
outer:
|
||||
for (SMUserProvisioning user : users) {
|
||||
Map<String, String> metaParameters = user.getMetaParameters();
|
||||
String effectiveUserId = user.getUserId();
|
||||
if (CommonUtils.isNotEmpty(metaParameters.get(SMStandardMeta.META_USER_ID))) {
|
||||
effectiveUserId = metaParameters.get(SMStandardMeta.META_USER_ID);
|
||||
}
|
||||
String effectiveUserId = extractUserId(user);
|
||||
String effectiveUserIdLowerCase = effectiveUserId.toLowerCase();
|
||||
if (seen.contains(effectiveUserIdLowerCase)) {
|
||||
log.warn("Skipping duplicate user (case-insensitive): " + effectiveUserId);
|
||||
@@ -434,25 +431,109 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
seen.add(effectiveUserIdLowerCase);
|
||||
|
||||
String authRole = user.getAuthRole() == null ? userImportList.getAuthRole() : user.getAuthRole();
|
||||
String userId = effectiveUserId;
|
||||
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
|
||||
String targetUserId = null;
|
||||
boolean subjectConflict = false;
|
||||
for (String possibleUserId : List.of(effectiveUserId, effectiveUserId.toLowerCase())) {
|
||||
if (isSubjectExists(possibleUserId)) {
|
||||
if (getSubjectType(possibleUserId) == SMSubjectType.team) {
|
||||
log.error("Cannot import user '%s': a team with this name already exists.".formatted(possibleUserId));
|
||||
subjectConflict = true;
|
||||
} else {
|
||||
log.info("User already exist : " + possibleUserId);
|
||||
setUserAuthRole(connection, possibleUserId, authRole);
|
||||
enableUser(connection, possibleUserId, true, null, null);
|
||||
targetUserId = possibleUserId;
|
||||
}
|
||||
continue outer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
insertUser(connection, userId.toLowerCase(), metaParameters, true, authRole);
|
||||
importedUsers.add(user);
|
||||
if (subjectConflict) {
|
||||
continue;
|
||||
}
|
||||
if (targetUserId == null) {
|
||||
targetUserId = effectiveUserId.toLowerCase();
|
||||
insertUser(connection, targetUserId, user.getMetaParameters(), true, authRole);
|
||||
importedUsers.add(user);
|
||||
}
|
||||
if (CommonUtils.isNotEmpty(userImportList.getAuthProviderId())) {
|
||||
linkProvisionedAuthProvider(connection, targetUserId, userImportList.getAuthProviderId());
|
||||
}
|
||||
}
|
||||
return importedUsers;
|
||||
}
|
||||
|
||||
protected void linkProvisionedAuthProvider(
|
||||
@NotNull Connection connection,
|
||||
@NotNull String userId,
|
||||
@NotNull String authProviderId
|
||||
) throws SQLException {
|
||||
try (PreparedStatement dbStat = connection.prepareStatement(
|
||||
"SELECT USER_ID FROM {table_prefix}CB_USER WHERE USER_ID=?"
|
||||
)) {
|
||||
dbStat.setString(1, userId);
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SQLException("User '" + userId + "' not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
try (PreparedStatement dbStat = connection.prepareStatement(
|
||||
"SELECT CRED_ID FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?"
|
||||
)) {
|
||||
dbStat.setString(1, userId);
|
||||
dbStat.setString(2, authProviderId);
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
if (dbResult.next()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
JDBCUtils.executeStatement(
|
||||
connection,
|
||||
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)",
|
||||
userId,
|
||||
authProviderId,
|
||||
CRED_ID_PROVISIONED,
|
||||
CRED_VALUE_PROVISIONED
|
||||
);
|
||||
}
|
||||
|
||||
protected void linkProvisionedAuthProviders(@NotNull SMUserImportList userImportList) throws DBException {
|
||||
String authProviderId = userImportList.getAuthProviderId();
|
||||
if (CommonUtils.isEmpty(authProviderId)) {
|
||||
return;
|
||||
}
|
||||
try (Connection connection = database.openConnection();
|
||||
JDBCTransaction transaction = new JDBCTransaction(connection)) {
|
||||
for (SMUserProvisioning user : userImportList.getUsers()) {
|
||||
String userId = extractUserId(user);
|
||||
String existingUserId = null;
|
||||
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
|
||||
if (isSubjectExists(possibleUserId) && getSubjectType(possibleUserId) == SMSubjectType.user) {
|
||||
existingUserId = possibleUserId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (existingUserId == null) {
|
||||
throw new DBException("Imported user '" + userId + "' not found");
|
||||
}
|
||||
linkProvisionedAuthProvider(connection, existingUserId, authProviderId);
|
||||
}
|
||||
transaction.commit();
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error linking provisioned auth provider", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String extractUserId(SMUserProvisioning user) {
|
||||
String metaUserId = user.getMetaParameters().get(SMStandardMeta.META_USER_ID);
|
||||
if (CommonUtils.isNotEmpty(metaUserId)) {
|
||||
return metaUserId;
|
||||
}
|
||||
return user.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(String userId) throws DBCException {
|
||||
invalidateAllUserTokens(userId);
|
||||
@@ -1173,12 +1254,28 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
@NotNull String authProviderId,
|
||||
@NotNull Map<String, Object> credentials
|
||||
) throws DBException {
|
||||
var existUserByCredentials = findUserByCredentials(getAuthProvider(authProviderId), credentials, false);
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
setUserCredentials(dbCon, userId, authProviderId, credentials);
|
||||
txn.commit();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error saving user credentials in database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setUserCredentials(
|
||||
@NotNull Connection dbCon,
|
||||
@NotNull String userId,
|
||||
@NotNull String authProviderId,
|
||||
@NotNull Map<String, Object> credentials
|
||||
) throws DBException, SQLException {
|
||||
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
|
||||
var existUserByCredentials = findUserByCredentials(dbCon, authProvider, credentials, false);
|
||||
if (existUserByCredentials != null && !existUserByCredentials.equals(userId)) {
|
||||
throw new DBException("Another user is already linked to the specified credentials");
|
||||
}
|
||||
List<String[]> transformedCredentials;
|
||||
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
|
||||
if (authProvider.isCaseInsensitive() && !isSubjectExists(userId) && isSubjectExists(userId.toLowerCase())) {
|
||||
log.warn("User with id '" + userId + "' not found, credentials will be set for the user: " + userId.toLowerCase());
|
||||
userId = userId.toLowerCase();
|
||||
@@ -1199,35 +1296,28 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
} catch (Exception e) {
|
||||
throw new DBCException(e.getMessage(), e);
|
||||
}
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
JDBCUtils.executeStatement(
|
||||
dbCon,
|
||||
"DELETE FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?",
|
||||
userId,
|
||||
authProvider.getId()
|
||||
);
|
||||
if (!CommonUtils.isEmpty(credentials)) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS" +
|
||||
"(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)")
|
||||
) {
|
||||
for (String[] cred : transformedCredentials) {
|
||||
if (cred == null) {
|
||||
continue;
|
||||
}
|
||||
dbStat.setString(1, userId);
|
||||
dbStat.setString(2, authProvider.getId());
|
||||
dbStat.setString(3, cred[0]);
|
||||
dbStat.setString(4, cred[1]);
|
||||
dbStat.execute();
|
||||
}
|
||||
JDBCUtils.executeStatement(
|
||||
dbCon,
|
||||
"DELETE FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?",
|
||||
userId,
|
||||
authProvider.getId()
|
||||
);
|
||||
if (!CommonUtils.isEmpty(credentials)) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS" +
|
||||
"(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)")
|
||||
) {
|
||||
for (String[] cred : transformedCredentials) {
|
||||
if (cred == null) {
|
||||
continue;
|
||||
}
|
||||
dbStat.setString(1, userId);
|
||||
dbStat.setString(2, authProvider.getId());
|
||||
dbStat.setString(3, cred[0]);
|
||||
dbStat.setString(4, cred[1]);
|
||||
dbStat.execute();
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error saving user credentials in database", e);
|
||||
}
|
||||
log.info(String.format("Set credentials for user: [userId=%s,providerId=%s]", userId, authProviderId));
|
||||
}
|
||||
@@ -1253,16 +1343,31 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
@NotNull Map<String, Object> authParameters,
|
||||
boolean onlyActive // throws exception if user is inactive
|
||||
) throws DBException {
|
||||
String userId = findUserByCredentials(authProvider, authParameters, onlyActive, false);
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
return findUserByCredentials(dbCon, authProvider, authParameters, onlyActive);
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error while searching credentials", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String findUserByCredentials(
|
||||
@NotNull Connection dbCon,
|
||||
@NotNull WebAuthProviderDescriptor authProvider,
|
||||
@NotNull Map<String, Object> authParameters,
|
||||
boolean onlyActive
|
||||
) throws DBCException {
|
||||
String userId = findUserByCredentials(dbCon, authProvider, authParameters, onlyActive, false);
|
||||
if (userId == null && authProvider.isCaseInsensitive()) {
|
||||
// try to find user id with lower case is auth provider is case-insensitive
|
||||
return findUserByCredentials(authProvider, authParameters, onlyActive, true);
|
||||
return findUserByCredentials(dbCon, authProvider, authParameters, onlyActive, true);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String findUserByCredentials(
|
||||
@NotNull Connection dbCon,
|
||||
@NotNull WebAuthProviderDescriptor authProvider,
|
||||
@NotNull Map<String, Object> authParameters,
|
||||
boolean onlyActive,
|
||||
@@ -1303,34 +1408,32 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
.append(joinAlias).append("CRED_ID=? AND ")
|
||||
.append(joinAlias).append("CRED_VALUE=?");
|
||||
}
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(sql.toString())) {
|
||||
dbStat.setString(1, authProvider.getId());
|
||||
int param = 2;
|
||||
for (Map.Entry<String, String> credEntry : identCredentials.entrySet()) {
|
||||
dbStat.setString(param++, credEntry.getKey());
|
||||
dbStat.setString(param++, credEntry.getValue());
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(sql.toString())) {
|
||||
dbStat.setString(1, authProvider.getId());
|
||||
int param = 2;
|
||||
for (Map.Entry<String, String> credEntry : identCredentials.entrySet()) {
|
||||
dbStat.setString(param++, credEntry.getKey());
|
||||
dbStat.setString(param++, credEntry.getValue());
|
||||
}
|
||||
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
String userId = null;
|
||||
boolean isActive = false;
|
||||
while (dbResult.next()) {
|
||||
String credUserId = dbResult.getString(1);
|
||||
isActive = CHAR_BOOL_TRUE.equals(dbResult.getString(2));
|
||||
if (userId == null) {
|
||||
userId = credUserId;
|
||||
} else if (!userId.equals(credUserId)) {
|
||||
log.error("Multiple users associated with the same credentials! " + credUserId + ", " + userId);
|
||||
}
|
||||
}
|
||||
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
String userId = null;
|
||||
boolean isActive = false;
|
||||
while (dbResult.next()) {
|
||||
String credUserId = dbResult.getString(1);
|
||||
isActive = CHAR_BOOL_TRUE.equals(dbResult.getString(2));
|
||||
if (userId == null) {
|
||||
userId = credUserId;
|
||||
} else if (!userId.equals(credUserId)) {
|
||||
log.error("Multiple users associated with the same credentials! " + credUserId + ", " + userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userId != null && onlyActive && !isActive) {
|
||||
throw new DBCException("User account is locked");
|
||||
}
|
||||
|
||||
return userId;
|
||||
if (userId != null && onlyActive && !isActive) {
|
||||
throw new DBCException("User account is locked");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error while searching credentials", e);
|
||||
@@ -1341,7 +1444,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
public Map<String, Object> getUserCredentials(String userId, String authProviderId) throws DBCException {
|
||||
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
|
||||
Map<String, Object> creds = getUserCredentials(authProvider, userId);
|
||||
if (creds.isEmpty() && authProvider.isCaseInsensitive()) {
|
||||
if (creds.isEmpty() && authProvider.isCaseInsensitive() && !isSubjectExists(userId)) {
|
||||
return getUserCredentials(authProvider, userId.toLowerCase());
|
||||
}
|
||||
return creds;
|
||||
@@ -1352,10 +1455,11 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT CRED_ID,CRED_VALUE FROM {table_prefix}CB_USER_CREDENTIALS\n" +
|
||||
"WHERE USER_ID=? AND PROVIDER_ID=?")) {
|
||||
"WHERE USER_ID=? AND PROVIDER_ID=? AND CRED_ID<>?")) {
|
||||
dbStat.setString(1, userId);
|
||||
|
||||
dbStat.setString(2, authProvider.getId());
|
||||
dbStat.setString(3, CRED_ID_PROVISIONED);
|
||||
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
Map<String, Object> credentials = new LinkedHashMap<>();
|
||||
|
||||
+11
-7
@@ -51,13 +51,17 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
import org.jkiss.utils.SecurityUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Driver;
|
||||
import java.sql.PreparedStatement;
|
||||
@@ -197,11 +201,11 @@ public class CBDatabase extends InternalDB<WebDatabaseConfig> {
|
||||
if (driver.isAnonymousAccess()) {
|
||||
return;
|
||||
}
|
||||
File pwdFile = application.getDataDirectory(true).resolve(DEFAULT_DB_PWD_FILE).toFile();
|
||||
Path pwdFile = application.getDataDirectory(true).resolve(DEFAULT_DB_PWD_FILE);
|
||||
// Load or generate random password
|
||||
if (pwdFile.exists()) {
|
||||
try (FileReader fr = new FileReader(pwdFile)) {
|
||||
databaseConfig.setPassword(IOUtils.readToString(fr));
|
||||
if (Files.exists(pwdFile)) {
|
||||
try {
|
||||
databaseConfig.setPassword(Files.readString(pwdFile));
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
@@ -209,7 +213,7 @@ public class CBDatabase extends InternalDB<WebDatabaseConfig> {
|
||||
if (CommonUtils.isEmpty(databaseConfig.getPassword())) {
|
||||
databaseConfig.setPassword(SecurityUtils.generatePassword(8));
|
||||
try {
|
||||
IOUtils.writeFileFromString(pwdFile, databaseConfig.getPassword());
|
||||
Files.writeString(pwdFile, databaseConfig.getPassword());
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: CloudBeaver SLF4j Binding
|
||||
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
|
||||
Bundle-Version: 1.0.67.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Version: 1.0.69.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Fragment-Host: slf4j.api
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.slf4j</artifactId>
|
||||
<version>1.0.67-SNAPSHOT</version>
|
||||
<version>1.0.69-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<dependency>
|
||||
<groupId>com.clickhouse</groupId>
|
||||
<artifactId>clickhouse-jdbc</artifactId>
|
||||
<version>0.10.0-rc1</version>
|
||||
<version>0.10.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
@@ -31,10 +31,26 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.yawk.lz4</groupId>
|
||||
<artifactId>lz4-java</artifactId>
|
||||
<version>1.11.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<artifactId>httpcore5</artifactId>
|
||||
<version>5.4.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<artifactId>httpcore5-h2</artifactId>
|
||||
<version>5.4.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<version>5.4.4</version>
|
||||
<version>5.6.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -42,11 +58,6 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.yawk.lz4</groupId>
|
||||
<artifactId>lz4-java</artifactId>
|
||||
<version>1.10.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.ce.drivers.feature"
|
||||
label="Cloudbeaver CE 3rd Party Drivers"
|
||||
version="1.0.175.qualifier"
|
||||
version="1.0.177.qualifier"
|
||||
provider-name="DBeaver Corp">
|
||||
|
||||
<plugin id="io.cloudbeaver.resources.drivers.base" version="0.0.0"/>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
|
||||
<version>1.0.175-SNAPSHOT</version>
|
||||
<version>1.0.177-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.product.ce.feature"
|
||||
label="%featureName"
|
||||
version="26.1.5.qualifier"
|
||||
version="26.2.1.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.product.ce">
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<version>26.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
|
||||
<build>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user