mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
99
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d71a77a48 | ||
|
|
e6e92f0bb3 | ||
|
|
5a394e648d | ||
|
|
e466462269 | ||
|
|
1c57e670c0 | ||
|
|
3808af50c7 | ||
|
|
390452d8a6 | ||
|
|
b733f395d3 | ||
|
|
e31ae9a439 | ||
|
|
07f60c0034 | ||
|
|
ba32c12b9c | ||
|
|
ca3ce9b79d | ||
|
|
96d6dba83a | ||
|
|
6ce5ef235c | ||
|
|
a1404e3495 | ||
|
|
d4e2f2365a | ||
|
|
ab813e21ab | ||
|
|
c5190b2567 | ||
|
|
e896789cb0 | ||
|
|
60827d769a | ||
|
|
65876e9b4e | ||
|
|
048206266d | ||
|
|
f41b9d24fe | ||
|
|
03dabc0e1b | ||
|
|
56946742c2 | ||
|
|
e1e4ae0ba6 | ||
|
|
80f258b561 | ||
|
|
ac48e13039 | ||
|
|
3877e914c4 | ||
|
|
564ceb7d82 | ||
|
|
622b5133c2 | ||
|
|
376575efce | ||
|
|
980e169ee1 | ||
|
|
e9cbaeadd9 | ||
|
|
66764292c9 | ||
|
|
2e2a61a330 | ||
|
|
edda6d8db3 | ||
|
|
c9f4ddd382 | ||
|
|
fb0bdc1017 | ||
|
|
123dc7777c | ||
|
|
2963610498 | ||
|
|
d1e07be46f | ||
|
|
043204002f | ||
|
|
c8c82c7f3e | ||
|
|
a0944850b1 | ||
|
|
31841b7720 | ||
|
|
a2695f3102 | ||
|
|
3c7c1ffc8e | ||
|
|
55c776f3e4 | ||
|
|
8a4d88e721 | ||
|
|
426fd40c82 | ||
|
|
a547b85ef2 | ||
|
|
e32e6eac39 | ||
|
|
901669b802 | ||
|
|
69d7a25fec | ||
|
|
576028b367 | ||
|
|
9276777c3e | ||
|
|
4ff45220ee | ||
|
|
62ad5a61c9 | ||
|
|
587a9e58e1 | ||
|
|
4401cfe92f | ||
|
|
59e33e8c48 | ||
|
|
d16c7d4502 | ||
|
|
a51765f516 | ||
|
|
27d8f4c011 | ||
|
|
0bb7803565 | ||
|
|
0727974c64 | ||
|
|
cabacfb475 | ||
|
|
fea2d78ef2 | ||
|
|
ddf94efef0 | ||
|
|
c3be008ba2 | ||
|
|
7acbbfe4ec | ||
|
|
7e0ad33fa4 | ||
|
|
d9d30a0e35 | ||
|
|
94e70f2d16 | ||
|
|
9ee4701fd0 | ||
|
|
9395659666 | ||
|
|
13a6dc9da7 | ||
|
|
36071c244e | ||
|
|
3e722ce3b7 | ||
|
|
3c9ddcdee0 | ||
|
|
0b589734e8 | ||
|
|
b8db43573b | ||
|
|
7c1535a95a | ||
|
|
900be41734 | ||
|
|
0a50d841d0 | ||
|
|
00b214b8bc | ||
|
|
ea6c960353 | ||
|
|
44c0cfe856 | ||
|
|
470d64718b | ||
|
|
0c0669f0c7 | ||
|
|
2a012d600c | ||
|
|
0b2370d414 | ||
|
|
a0def9f3ae | ||
|
|
13cf2911e0 | ||
|
|
c0c3594633 | ||
|
|
180ec65dd9 | ||
|
|
79d5702744 | ||
|
|
603b94c551 |
@@ -49,21 +49,14 @@ jobs:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: |
|
||||
cloudbeaver/common-*/yarn.lock
|
||||
cloudbeaver/webapp/yarn.lock
|
||||
|
||||
- name: restore cloudbeaver-common typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/common-*/**/*/lib"
|
||||
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-common-dist-
|
||||
|
||||
- name: restore typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/webapp/**/packages/*/dist"
|
||||
path: |
|
||||
"cloudbeaver/webapp/common-*/**/*/lib"
|
||||
"cloudbeaver/webapp/**/packages/*/dist"
|
||||
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dist-
|
||||
@@ -71,23 +64,11 @@ jobs:
|
||||
- if: inputs.clean-cache
|
||||
run: yarn clear
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- run: yarn bundle
|
||||
working-directory: ./cloudbeaver/webapp/packages/product-default
|
||||
|
||||
- run: yarn test
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
# - run: yarn test
|
||||
# working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn test
|
||||
|
||||
# - name: Archive build artifacts
|
||||
|
||||
@@ -30,10 +30,10 @@ jobs:
|
||||
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-typescript
|
||||
# working-directory: ./cloudbeaver/webapp/common-typescript
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-react
|
||||
# working-directory: ./cloudbeaver/webapp/common-react
|
||||
|
||||
- run: |
|
||||
yarn install --immutable
|
||||
|
||||
Vendored
+12
-2
@@ -26,7 +26,17 @@
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-typescript"
|
||||
"cwd": "${workspaceFolder}/webapp/common-typescript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Tests Common React",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/common-react"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -123,7 +133,7 @@
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
|
||||
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
"args": ["docs"]
|
||||
}
|
||||
|
||||
Vendored
Vendored
Vendored
+2
-2
@@ -2,6 +2,6 @@
|
||||
<?pde version="3.8"?>
|
||||
<target name="org.jkiss.cloudbeaver.tp">
|
||||
<locations>
|
||||
<location path="../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
|
||||
<location path="../../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
|
||||
</locations>
|
||||
</target>
|
||||
</target>
|
||||
@@ -25,56 +25,37 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.1.3 2025-07-21
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
|
||||
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Viewer;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
|
||||
|
||||
### 25.1.2 2025-07-07
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added a button “Clear” to the Output panel;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- PostgresSQL: fixed misplaced comment for table DDL generation.
|
||||
|
||||
### 25.1.1 2025-06-23
|
||||
### 25.2.0 2025-09-01
|
||||
### Changes since 25.1.0:
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
|
||||
- Databend database support has been added.
|
||||
|
||||
### 25.1.0 2025-06-02
|
||||
### Changes since 25.0.0:
|
||||
- Important:
|
||||
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
|
||||
- Administration:
|
||||
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
|
||||
- LDAP authorization:
|
||||
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
|
||||
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
|
||||
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
|
||||
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
|
||||
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
|
||||
- SQL Editor:
|
||||
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
|
||||
- Auto-completion has been improved for aliases and camelCase entities.
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
|
||||
- A “Clear” button was added to the output panel.
|
||||
- Data Editor:
|
||||
- Improved table rendering to scale column width depending on the content;
|
||||
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
|
||||
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
|
||||
- Added an ability to review the script before execution when users edit tables without primary keys.
|
||||
- General:
|
||||
- Added the ability to configure the default database or schema in the connection configuration;
|
||||
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
|
||||
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
|
||||
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
|
||||
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
|
||||
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
|
||||
- Databases and drivers:
|
||||
- Clickhouse: driver has been updated to version 0.8.5;
|
||||
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
|
||||
- LibSQL: added the ability to connect using token authentication;
|
||||
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
|
||||
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
|
||||
- Databend database support has been added (thanks to @hantmac);
|
||||
- DuckDB: driver has been updated to version 1.3;
|
||||
- MySQL: Improved performance when retrieving foreign keys metadata;
|
||||
- PostgreSQL: fixed misplaced comment for table DDL generation.
|
||||
|
||||
## Contribution
|
||||
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
|
||||
|
||||
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
|
||||
The most significant contribution to our code for the major release 25.1.0 was made by:
|
||||
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
|
||||
The most significant contribution to our code for the major release 25.2.0 was made by:
|
||||
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.6.2-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { type ComponentPropsWithRef } from 'react';
|
||||
import './Input.css';
|
||||
import clsx from 'clsx';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
|
||||
export interface InputProps extends Omit<ComponentPropsWithRef<'input'>, 'size'> {
|
||||
size?: 'small' | 'medium' | 'large' | 'xlarge';
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const InputBase: React.FC<InputProps> = function Input({ size, className, ...props }) {
|
||||
const classNameToApply = clsx(`dbv-kit-input`, `dbv-kit-input--${size ?? 'medium'}`, className);
|
||||
return (
|
||||
<label className="dbv-kit-input-wrapper">
|
||||
{props.label && <div className={clsx('dbv-kit-input__title', props.required && 'dbv-kit-input__title--required')}>{props.label}</div>}
|
||||
<input className={classNameToApply} {...props} />
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export const Input = componentProviderWrapper('Input', InputBase);
|
||||
File diff suppressed because it is too large
Load Diff
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.6.2-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
server: {
|
||||
serverPort: "${CLOUDBEAVER_WEB_SERVER_PORT:8978}",
|
||||
secureCookies: "${CLOUDBEAVER_SECURE_COOKIES:true}",
|
||||
forceHttps: "${CLOUDBEAVER_FORCE_HTTPS:false}",
|
||||
|
||||
contentRoot: "web",
|
||||
driversLocation: "drivers",
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
rootURI: "${CLOUDBEAVER_ROOT_URI:/}",
|
||||
serviceURI: "/api/",
|
||||
supportedHosts: [],
|
||||
|
||||
productSettings: {
|
||||
# Global properties
|
||||
@@ -27,7 +28,7 @@
|
||||
},
|
||||
|
||||
expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
|
||||
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:true}",
|
||||
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:disable}",
|
||||
develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
|
||||
|
||||
enableSecurityManager: false,
|
||||
|
||||
@@ -5,10 +5,8 @@ echo "Build static content"
|
||||
|
||||
mkdir -p ./cloudbeaver/web
|
||||
|
||||
cd ../../cloudbeaver/common-typescript
|
||||
cd ../../cloudbeaver/webapp
|
||||
|
||||
yarn install --immutable
|
||||
cd ../webapp
|
||||
yarn install --immutable
|
||||
cd ./packages/product-default
|
||||
yarn run bundle
|
||||
@@ -17,9 +15,7 @@ if [[ "$?" -ne 0 ]] ; then
|
||||
echo 'Application build failed'; exit $rc
|
||||
fi
|
||||
|
||||
cd ../../../common-typescript
|
||||
yarn test
|
||||
cd ../webapp
|
||||
cd ../../
|
||||
yarn test
|
||||
|
||||
if [[ "$?" -ne 0 ]] ; then
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
lefthook: |
|
||||
cd webapp
|
||||
yarn lefthook
|
||||
yarn dlx --quiet lefthook
|
||||
output: false
|
||||
pre-commit:
|
||||
commands:
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.82.qualifier
|
||||
Bundle-Release-Date: 20250804
|
||||
Bundle-Version: 1.0.85.qualifier
|
||||
Bundle-Release-Date: 20250922
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
|
||||
@@ -10,4 +10,10 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.deploymentType.n
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbUrl.name=Security manager database URL
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbDriverName.name=Security manager database driver name
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.name=Security manager database product name
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
|
||||
@@ -0,0 +1,2 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Garder actif (en secondes)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Pas de déconnexion automatique
|
||||
@@ -0,0 +1,5 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
|
||||
@@ -0,0 +1,4 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
|
||||
@@ -0,0 +1,3 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.82-SNAPSHOT</version>
|
||||
<version>1.0.85-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -29,8 +29,12 @@ public interface DBWConstants {
|
||||
String PERMISSION_CONFIGURATION_MANAGER = RMConstants.PERMISSION_CONFIGURATION_MANAGER;
|
||||
String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access";
|
||||
String PERMISSION_SECRET_MANAGER = "secret-manager";
|
||||
String PERMISSION_SQL_RESULT_UPDATE = "sql-result-update";
|
||||
String PERMISSION_SQL_EXECUTE_QUERY = "sql-execute-query";
|
||||
|
||||
String GLOBAL_PERMISSION_SCRIPT_EXECUTE = "permission.sql.script.execution";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_IMPORT = "permission.data-editor.import";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_EDITING = "permission.data-editor.editing";
|
||||
|
||||
String PERMISSION_EDIT_STRUCTURE = "edit-meta";
|
||||
String PERMISSION_EDIT_DATA = "edit-data";
|
||||
|
||||
@@ -36,4 +36,6 @@ public interface DBWFeatureSet {
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isEnabledByDefault();
|
||||
|
||||
}
|
||||
|
||||
+31
-7
@@ -17,6 +17,7 @@
|
||||
|
||||
package io.cloudbeaver;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -34,12 +35,11 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.secret.DBSSecretController;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
|
||||
import org.jkiss.dbeaver.registry.DataSourceConfigurationManager;
|
||||
import org.jkiss.dbeaver.registry.DataSourceParseResults;
|
||||
import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -48,12 +48,19 @@ import java.util.stream.Collectors;
|
||||
* We need to filter some data sources in case of inaccessibility (not enough permissions).
|
||||
*/
|
||||
public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, DataSourcePersistentRegistry, DBPDataSourceRegistryCache {
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final DataSourceFilter dataSourceFilter;
|
||||
@NotNull
|
||||
private final DataSourceRegistry<?> dataSourceRegistry;
|
||||
|
||||
public WebGlobalProjectRegistryProxy(@NotNull DataSourceRegistry<?> dataSourceRegistry, @NotNull DataSourceFilter filter) {
|
||||
public WebGlobalProjectRegistryProxy(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull DataSourceRegistry<?> dataSourceRegistry,
|
||||
@NotNull DataSourceFilter filter
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.dataSourceRegistry = dataSourceRegistry;
|
||||
this.dataSourceFilter = filter;
|
||||
}
|
||||
@@ -181,13 +188,30 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
|
||||
@NotNull
|
||||
@Override
|
||||
public List<? extends DBPDataSourceFolder> getAllFolders() {
|
||||
return dataSourceRegistry.getAllFolders();
|
||||
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
return dataSourceRegistry.getAllFolders();
|
||||
}
|
||||
Set<DBPDataSourceFolder> set = new LinkedHashSet<>();
|
||||
for (DBPDataSourceContainer container : getDataSources()) {
|
||||
DBPDataSourceFolder folder = container.getFolder();
|
||||
while (folder != null) {
|
||||
set.add(folder);
|
||||
folder = folder.getParent();
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(set);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<? extends DBPDataSourceFolder> getRootFolders() {
|
||||
return dataSourceRegistry.getRootFolders();
|
||||
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
return dataSourceRegistry.getRootFolders();
|
||||
}
|
||||
return getDataSources().stream()
|
||||
.map(DBPDataSourceContainer::getFolder)
|
||||
.filter(folder -> folder != null && folder.getParent() == null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -354,7 +378,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadDataSources(
|
||||
public DataSourceParseResults loadDataSources(
|
||||
@NotNull List<DBPDataSourceConfigurationStorage> storages,
|
||||
@NotNull DataSourceConfigurationManager manager,
|
||||
@Nullable Collection<String> dataSourceIds, boolean refresh,
|
||||
|
||||
+1
@@ -50,6 +50,7 @@ public class WebSessionGlobalProjectImpl extends WebSessionProjectImpl {
|
||||
@Override
|
||||
protected DBPDataSourceRegistry createDataSourceRegistry() {
|
||||
return new WebGlobalProjectRegistryProxy(
|
||||
webSession,
|
||||
createRegistryWithCredentialsProvider(),
|
||||
this::isDataSourceAccessible
|
||||
);
|
||||
|
||||
@@ -29,13 +29,16 @@ import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
|
||||
@@ -194,40 +197,31 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
/**
|
||||
* updates data sources based on event in web session
|
||||
*
|
||||
* @param dataSourceIds list of updated connections
|
||||
* @param eventId id of event
|
||||
* @param event data source updated event
|
||||
*/
|
||||
public synchronized boolean updateProjectDataSources(@NotNull List<String> dataSourceIds, @NotNull String eventId) {
|
||||
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
|
||||
var sendDataSourceUpdatedEvent = false;
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
// save old connections
|
||||
var oldDataSources = dataSourceIds.stream()
|
||||
.map(registry::getDataSource)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
DBPDataSourceContainer::getId,
|
||||
registry::createDataSource)
|
||||
);
|
||||
if (WSDataSourceEvent.CREATED.equals(eventId) || WSDataSourceEvent.UPDATED.equals(eventId)) {
|
||||
registry.refreshConfig(dataSourceIds);
|
||||
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
registry.refreshConfig(event.getDataSourceIds());
|
||||
}
|
||||
for (String dsId : dataSourceIds) {
|
||||
for (String dsId : event.getDataSourceIds()) {
|
||||
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
|
||||
if (ds == null) {
|
||||
continue;
|
||||
}
|
||||
switch (eventId) {
|
||||
switch (event.getId()) {
|
||||
case WSDataSourceEvent.CREATED -> {
|
||||
addConnection(ds);
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
case WSDataSourceEvent.UPDATED -> {
|
||||
boolean connectionUpdated = !ds.equalSettings(oldDataSources.get(dsId));
|
||||
if (connectionUpdated) {
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds);
|
||||
}
|
||||
// if settings were changed we need to send event
|
||||
if (event.getProperty() != WSDataSourceProperty.INTERNAL) {
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
}
|
||||
case WSDataSourceEvent.DELETED -> {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds);
|
||||
|
||||
+24
-6
@@ -56,7 +56,9 @@ public class WebConnectionConfig {
|
||||
private boolean saveCredentials;
|
||||
private boolean sharedCredentials;
|
||||
private Map<String, Object> mainPropertyValues;
|
||||
private Map<String, Object> expertSettingsValues;
|
||||
private Map<String, Object> providerProperties;
|
||||
private Map<String, Object> externalParameters;
|
||||
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
|
||||
private DBPDriverConfigurationType configurationType;
|
||||
private String selectedSecretId;
|
||||
@@ -71,7 +73,6 @@ public class WebConnectionConfig {
|
||||
if (!CommonUtils.isEmpty(params)) {
|
||||
connectionId = JSONUtils.getString(params, "connectionId");
|
||||
driverId = JSONUtils.getString(params, "driverId");
|
||||
readOnly = JSONUtils.getBoolean(params, "readOnly");
|
||||
|
||||
host = JSONUtils.getString(params, "host");
|
||||
port = JSONUtils.getString(params, "port");
|
||||
@@ -79,9 +80,6 @@ public class WebConnectionConfig {
|
||||
databaseName = JSONUtils.getString(params, "databaseName");
|
||||
url = JSONUtils.getString(params, "url");
|
||||
|
||||
keepAliveInterval = JSONUtils.getInteger(params, "keepAliveInterval", -1);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(params, "autocommit", true);
|
||||
|
||||
name = JSONUtils.getString(params, "name");
|
||||
description = JSONUtils.getString(params, "description");
|
||||
folder = JSONUtils.getString(params, "folder");
|
||||
@@ -98,11 +96,22 @@ public class WebConnectionConfig {
|
||||
|
||||
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
|
||||
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
|
||||
defaultCatalogName = JSONUtils.getString(params, "defaultCatalogName");
|
||||
defaultSchemaName = JSONUtils.getString(params, "defaultSchemaName");
|
||||
|
||||
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
|
||||
keepAliveInterval = JSONUtils.getInteger(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
|
||||
readOnly = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
defaultCatalogName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
|
||||
defaultSchemaName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA);
|
||||
|
||||
String configType = JSONUtils.getString(params, "configurationType");
|
||||
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
|
||||
externalParameters = JSONUtils.getObjectOrNull(params, "externalParameters");
|
||||
|
||||
networkHandlersConfig = new ArrayList<>();
|
||||
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
|
||||
@@ -219,6 +228,11 @@ public class WebConnectionConfig {
|
||||
return mainPropertyValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
return expertSettingsValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getProviderProperties() {
|
||||
return providerProperties;
|
||||
@@ -248,4 +262,8 @@ public class WebConnectionConfig {
|
||||
public String getDefaultSchemaName() {
|
||||
return defaultSchemaName;
|
||||
}
|
||||
|
||||
public Map<String, Object> getExternalParameters() {
|
||||
return externalParameters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,6 +417,17 @@ public class WebConnectionInfo {
|
||||
return mainProperties;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
Map<String, Object> expertSettings = new LinkedHashMap<>();
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA, getDefaultSchemaName());
|
||||
return expertSettings;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, String> getProviderProperties() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
/**
|
||||
* Web expert settings properties. Class for returning filtered expert settings properties in web interface.
|
||||
*/
|
||||
public class WebExpertSettingsProperties implements DBPObject {
|
||||
public static final String PROP_READ_ONLY = "readOnly";
|
||||
public static final String PROP_AUTO_COMMIT = "autocommit";
|
||||
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
|
||||
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
|
||||
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
|
||||
|
||||
private final DBPDriver driver;
|
||||
|
||||
public WebExpertSettingsProperties(@NotNull DBPDriver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@Property(order = 1, id = PROP_KEEP_ALIVE_INTERVAL, visibleIf = KeepAliveIntervalFieldValidator.class)
|
||||
public int getKeepAliveInterval() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
|
||||
public boolean isAutoCommit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
|
||||
public String getDefaultCatalog() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
|
||||
public String getDefaultSchema() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -37,6 +37,7 @@ public interface ApiCallInterceptor {
|
||||
@NotNull HttpServletRequest request,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull String apiCall,
|
||||
@Nullable String userId,
|
||||
@NotNull LocalDateTime startTime,
|
||||
@Nullable String errorMessage,
|
||||
@NotNull String apiProtocol
|
||||
|
||||
+13
@@ -36,6 +36,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
|
||||
protected boolean secretManagerEnabled;
|
||||
protected boolean showReadOnlyConnectionInfo;
|
||||
protected String[] enabledFeatures;
|
||||
protected String[] disabledFeatures;
|
||||
protected String[] disabledBetaFeatures;
|
||||
|
||||
|
||||
@@ -43,6 +44,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
|
||||
this.plugins = new LinkedHashMap<>();
|
||||
this.resourceManagerEnabled = true;
|
||||
this.enabledFeatures = null;
|
||||
this.disabledFeatures = new String[0];
|
||||
this.disabledBetaFeatures = new String[0];
|
||||
this.showReadOnlyConnectionInfo = false;
|
||||
this.secretManagerEnabled = false;
|
||||
@@ -53,6 +55,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
|
||||
this.defaultUserTeam = src.defaultUserTeam;
|
||||
this.resourceManagerEnabled = src.resourceManagerEnabled;
|
||||
this.enabledFeatures = src.enabledFeatures;
|
||||
this.disabledFeatures = src.disabledFeatures;
|
||||
this.disabledBetaFeatures = src.disabledBetaFeatures;
|
||||
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
|
||||
this.secretManagerEnabled = src.secretManagerEnabled;
|
||||
@@ -122,6 +125,16 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
|
||||
this.enabledFeatures = enabledFeatures;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String[] getDisabledFeatures() {
|
||||
return disabledFeatures;
|
||||
}
|
||||
|
||||
public void setDisabledFeatures(@NotNull String[] disabledFeatures) {
|
||||
this.disabledFeatures = disabledFeatures;
|
||||
}
|
||||
|
||||
public boolean isShowReadOnlyConnectionInfo() {
|
||||
return showReadOnlyConnectionInfo;
|
||||
}
|
||||
|
||||
+10
@@ -51,6 +51,16 @@ public interface ServletAppConfiguration {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns disabled features.
|
||||
*
|
||||
* @return array of disabled feature IDs
|
||||
*/
|
||||
@NotNull
|
||||
default String[] getDisabledFeatures() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
default boolean isSupportsCustomConnections() {
|
||||
return true;
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ import org.apache.commons.cli.CommandLine;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
|
||||
import org.jkiss.dbeaver.model.cli.CmdProcessResult;
|
||||
import org.jkiss.dbeaver.model.cli.CliProcessResult;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CmdProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
public CliProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
|
||||
try {
|
||||
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
|
||||
@@ -40,7 +40,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
|
||||
false
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return new CmdProcessResult(CmdProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
return new CliProcessResult(CliProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.model.cli;
|
||||
|
||||
public interface CloudbeaverCliConstants {
|
||||
String CLI_MODE = "cliMode";
|
||||
}
|
||||
+10
@@ -21,10 +21,12 @@ package io.cloudbeaver.model.config;
|
||||
*/
|
||||
public class ConnectionBruteForceConfiguration {
|
||||
private static final int DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES = 24 * 60;
|
||||
private static final int BLOCK_TIME_IN_MINUTES = 60;
|
||||
private static final int DEFAULT_MAX_CONNECT_ATTEMPTS = 5;
|
||||
private boolean enabled = false;
|
||||
private int maxFailedConnectAttempts = DEFAULT_MAX_CONNECT_ATTEMPTS;
|
||||
private int errorAttemptsPeriodInMinutes = DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES;
|
||||
private int blockTimeInMinutes = BLOCK_TIME_IN_MINUTES;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -49,4 +51,12 @@ public class ConnectionBruteForceConfiguration {
|
||||
public void setErrorAttemptsPeriodInMinutes(int errorAttemptsPeriodInMinutes) {
|
||||
this.errorAttemptsPeriodInMinutes = errorAttemptsPeriodInMinutes;
|
||||
}
|
||||
|
||||
public int getBlockTimeInMinutes() {
|
||||
return blockTimeInMinutes;
|
||||
}
|
||||
|
||||
public void setBlockTimeInMinutes(int blockTimeInMinutes) {
|
||||
this.blockTimeInMinutes = blockTimeInMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -149,27 +149,37 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
return updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected DataSourceParseResults updateProjectDataSourcesConfig(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
return doFileWriteOperation(projectId, project.getMetadataFolder(false),
|
||||
return doFileWriteOperation(
|
||||
projectId, project.getMetadataFolder(false),
|
||||
() -> {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer();
|
||||
var configChanged = ((DataSourcePersistentRegistry) registry).loadDataSources(
|
||||
final DataSourceParseResults parseResults = ((DataSourcePersistentRegistry) registry).loadDataSources(
|
||||
List.of(storage),
|
||||
manager,
|
||||
dataSourceIds,
|
||||
true,
|
||||
false
|
||||
dataSourceIds == null
|
||||
);
|
||||
registry.checkForErrors();
|
||||
log.debug("Save data sources configuration in project '" + projectId + "'");
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return configChanged;
|
||||
return parseResults;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -217,6 +227,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
var parent = result.getParent();
|
||||
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
|
||||
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -244,6 +255,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
|
||||
}
|
||||
}
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -265,6 +277,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
registry.moveFolder(oldPath, newPath);
|
||||
registry.checkForErrors();
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
+241
@@ -28,18 +28,26 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentials;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.model.websocket.event.MessageType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceParseResults;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
@@ -334,6 +342,239 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createProjectDataSources(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
super.createProjectDataSources(projectId, configuration, dataSourceIds);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
dataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateProjectDataSources(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
DBPDataSourceRegistry registry = getWebProject(projectId, false).getDataSourceRegistry();
|
||||
Map<String, DataSourceDescriptor> oldDataSources = registry.getDataSources().stream()
|
||||
.filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId()))
|
||||
.collect(Collectors.toMap(
|
||||
DBPDataSourceContainer::getId,
|
||||
registry::createDataSource
|
||||
)
|
||||
);
|
||||
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
|
||||
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
|
||||
return parseResults != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
|
||||
super.deleteProjectDataSources(projectId, dataSourceIds);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
Arrays.asList(dataSourceIds),
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createProjectDataSourceFolder(@NotNull String projectId, @NotNull String folderPath) throws DBException {
|
||||
super.createProjectDataSourceFolder(projectId, folderPath);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, folderPath))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveProjectDataSourceFolder(
|
||||
@NotNull String projectId,
|
||||
@NotNull String oldPath,
|
||||
@NotNull String newPath
|
||||
) throws DBException {
|
||||
super.moveProjectDataSourceFolder(projectId, oldPath, newPath);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, oldPath))
|
||||
)
|
||||
);
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, newPath))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProjectDataSourceFolders(@NotNull String projectId, @NotNull String[] folderPaths, boolean dropContents)
|
||||
throws DBException {
|
||||
super.deleteProjectDataSourceFolders(projectId, folderPaths, dropContents);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
Arrays.stream(folderPaths).map(
|
||||
p -> createNodePathFromFolderPath(projectId, p)
|
||||
).collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private String createNodePathFromFolderPath(String projectId, String folderPath) {
|
||||
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
|
||||
}
|
||||
|
||||
private void sendDataSourcesConfigUpdatedEvent(
|
||||
@NotNull DBPDataSourceRegistry registry,
|
||||
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
|
||||
@Nullable DataSourceParseResults parseResults
|
||||
) {
|
||||
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedNameDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, DataSourceDescriptor> entry : oldDataSources.entrySet()) {
|
||||
String dsId = entry.getKey();
|
||||
DataSourceDescriptor oldDs = entry.getValue();
|
||||
DataSourceDescriptor newDs = (DataSourceDescriptor) registry.getDataSource(dsId);
|
||||
if (newDs == null) {
|
||||
continue;
|
||||
}
|
||||
if (!oldDs.equalConfiguration(newDs)) {
|
||||
updatedConfigurationDataSourceIds.add(dsId);
|
||||
} else if (!oldDs.isLooselyEqualTo(newDs)) {
|
||||
updatedNameDataSourceIds.add(dsId);
|
||||
} else if (!oldDs.equalInternalConfiguration(newDs)) {
|
||||
updatedInternalConfigurationDataSourceIds.add(dsId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updatedConfigurationDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedConfigurationDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!updatedNameDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!updatedInternalConfigurationDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedInternalConfigurationDataSourceIds,
|
||||
WSDataSourceProperty.INTERNAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.addedDataSources.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.removedDataSources.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.addedFolders.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
parseResults.addedFolders.stream().map(
|
||||
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
|
||||
).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.removedFolders.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
parseResults.removedFolders.stream().map(
|
||||
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
|
||||
).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RMResource[] listResources(
|
||||
|
||||
+8
@@ -29,6 +29,7 @@ import org.jkiss.dbeaver.model.auth.SMAuthSpace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.security.user.SMTeam;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEventDeleteTempFile;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
@@ -36,6 +37,7 @@ import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -122,6 +124,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
userContext.refreshPermissions();
|
||||
if (userContext.isAuthorizedInSecurityManager()) {
|
||||
userContext.refreshAccessibleProjects();
|
||||
if (userContext.getUser() != null) {
|
||||
List<String> userTeamIds = Arrays.stream(userContext.getSecurityController().getCurrentUserTeams())
|
||||
.map(SMTeam::getTeamId)
|
||||
.toList();
|
||||
userContext.getUser().setTeams(userTeamIds.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
|
||||
+1
@@ -137,6 +137,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
|
||||
clearProjects();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
|
||||
return null;
|
||||
|
||||
@@ -77,6 +77,10 @@ public class WebUser {
|
||||
return user.getUserTeams();
|
||||
}
|
||||
|
||||
public void setTeams(@NotNull String[] teams) {
|
||||
user.setUserTeams(teams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return user.getUserId().hashCode();
|
||||
|
||||
+8
@@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.DBPImage;
|
||||
import org.jkiss.dbeaver.model.impl.AbstractContextDescriptor;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
/**
|
||||
* WebFeatureDescriptor
|
||||
@@ -35,6 +36,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
|
||||
private final String label;
|
||||
private final String description;
|
||||
private final DBPImage icon;
|
||||
private final boolean enabledByDefault;
|
||||
|
||||
public WebFeatureDescriptor(IConfigurationElement config)
|
||||
{
|
||||
@@ -43,6 +45,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
|
||||
this.label = config.getAttribute("label");
|
||||
this.description = config.getAttribute("description");
|
||||
this.icon = iconToImage(config.getAttribute("icon"));
|
||||
this.enabledByDefault = CommonUtils.getBoolean(config.getAttribute("enabledByDefault"), false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -69,4 +72,9 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
|
||||
return ServletAppUtils.getServletApplication().getAppConfiguration().isFeatureEnabled(this.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return enabledByDefault;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -68,4 +68,9 @@ public class WebServerFeatureDescriptor extends AbstractContextDescriptor implem
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ public class CBConstants {
|
||||
public static final String PARAM_SERVER_PORT = "serverPort";
|
||||
public static final String PARAM_SERVER_HOST = "serverHost";
|
||||
public static final String PARAM_SERVER_NAME = "serverName";
|
||||
public static final String PARAM_SECURE_COOKIES = "secureCookies";
|
||||
public static final String PARAM_FORCE_HTTPS = "forceHttps";
|
||||
public static final String PARAM_BIND_SESSION_TO_IP = "bindSessionToIp";
|
||||
public static final String PARAM_SUPPORTED_HOSTS = "supportedHosts";
|
||||
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
|
||||
public static final String PARAM_CONTENT_ROOT = "contentRoot";
|
||||
public static final String PARAM_SERVER_URL = "serverURL";
|
||||
@@ -93,4 +95,7 @@ public class CBConstants {
|
||||
public static final String ADMIN_AUTO_GRANT = "auto-grant";
|
||||
public static final String HOST_LOCALHOST = "localhost";
|
||||
public static final String HOST_127_0_0_1 = "127.0.0.1";
|
||||
|
||||
public static final String BIND_SESSION_ENABLE = "enable";
|
||||
public static final String BIND_SESSION_DISABLE = "disable";
|
||||
}
|
||||
|
||||
+9
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,8 +17,11 @@
|
||||
package io.cloudbeaver.service;
|
||||
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Servlet service
|
||||
*/
|
||||
@@ -28,4 +31,9 @@ public interface DBWServiceBindingServlet<APPLICATION extends ServletApplication
|
||||
}
|
||||
|
||||
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
|
||||
|
||||
@NotNull
|
||||
default List<String> getExcludedServletPaths(@NotNull APPLICATION application) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@@ -47,6 +48,11 @@ public class ServletAppUtils {
|
||||
|
||||
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
|
||||
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
|
||||
private static final Set<Integer> DEFAULT_PORTS = Set.of(
|
||||
80,
|
||||
443
|
||||
);
|
||||
|
||||
|
||||
private static final Log log = Log.getLog(ServletAppUtils.class);
|
||||
|
||||
@@ -286,7 +292,7 @@ public class ServletAppUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
|
||||
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
String origin = request.getHeader(HEADER_ORIGIN);
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
origin = request.getHeader(HEADER_X_ORIGIN);
|
||||
@@ -309,7 +315,26 @@ public class ServletAppUtils {
|
||||
if (!origin.endsWith(rootUri)) {
|
||||
origin = origin + "/" + rootUri + "/";
|
||||
}
|
||||
return removeSideSlashes(origin);
|
||||
|
||||
origin = removeSideSlashes(origin);
|
||||
URI uri = URI.create(origin);
|
||||
if (DEFAULT_PORTS.contains(uri.getPort())) {
|
||||
try {
|
||||
origin = new URI(
|
||||
uri.getScheme(),
|
||||
uri.getUserInfo(),
|
||||
uri.getHost(),
|
||||
-1,
|
||||
uri.getPath(),
|
||||
uri.getQuery(),
|
||||
uri.getFragment()
|
||||
).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
log.error("Failed to create URI without port", e);
|
||||
}
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
public static String getRootUrlFromUri(@NotNull URI uri) {
|
||||
@@ -328,7 +353,22 @@ public class ServletAppUtils {
|
||||
if (CommonUtils.isEmpty(rootUri)) {
|
||||
return uri;
|
||||
}
|
||||
String[] split = uri.split(rootUri);
|
||||
return split[0];
|
||||
rootUri = "/" + rootUri;
|
||||
if (!uri.endsWith(rootUri)) {
|
||||
rootUri = rootUri + "/";
|
||||
}
|
||||
return substringBeforeLast(uri, rootUri);
|
||||
}
|
||||
|
||||
public static String substringBeforeLast(
|
||||
@NotNull String string,
|
||||
@NotNull String separator
|
||||
) {
|
||||
if (CommonUtils.isNotEmpty(string) && CommonUtils.isNotEmpty(separator)) {
|
||||
int pos = string.lastIndexOf(separator);
|
||||
return pos == -1 ? string : string.substring(0, pos);
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -21,7 +21,6 @@ import io.cloudbeaver.model.WebConnectionFolderInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceFolder;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public class WebConnectionFolderUtils {
|
||||
@@ -45,9 +44,4 @@ public class WebConnectionFolderUtils {
|
||||
throw new DBWebException("Folder name '" + folderName + "' contains illegal characters: /");
|
||||
}
|
||||
}
|
||||
|
||||
public static DBPDataSourceFolder createFolder(WebConnectionFolderInfo parentFolder, String newName, DBPDataSourceRegistry registry) throws DBWebException {
|
||||
DBPDataSourceFolder folder = registry.addFolder(parentFolder == null ? null : parentFolder.getDataSourceFolder(), newName);
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,15 @@ package io.cloudbeaver.utils;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.security.SMSubjectType;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.permissions.WSSubjectPermissionEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceUpdatedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskInfoEvent;
|
||||
@@ -195,4 +198,19 @@ public class WebEventUtils {
|
||||
);
|
||||
}
|
||||
|
||||
public static void addSubjectPermissionsUpdateEvent(
|
||||
@NotNull String subjectId,
|
||||
@NotNull SMSubjectType subjectType,
|
||||
@Nullable String smSessionId,
|
||||
@Nullable String userId
|
||||
) {
|
||||
var event = WSSubjectPermissionEvent.update(
|
||||
smSessionId,
|
||||
userId,
|
||||
subjectType,
|
||||
subjectId
|
||||
);
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Community Product
|
||||
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
|
||||
Bundle-Version: 25.1.4.qualifier
|
||||
Bundle-Release-Date: 20250804
|
||||
Bundle-Version: 25.2.1.qualifier
|
||||
Bundle-Release-Date: 20250922
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>25.1.4-SNAPSHOT</version>
|
||||
<version>25.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.127.qualifier
|
||||
Bundle-Release-Date: 20250804
|
||||
Bundle-Version: 1.0.130.qualifier
|
||||
Bundle-Release-Date: 20250922
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
|
||||
<version>1.0.127-SNAPSHOT</version>
|
||||
<version>1.0.130-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,12 +3,13 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver CE Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
|
||||
Bundle-Version: 25.1.4.qualifier
|
||||
Bundle-Release-Date: 20250804
|
||||
Bundle-Version: 25.2.1.qualifier
|
||||
Bundle-Release-Date: 20250922
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
|
||||
Require-Bundle: io.cloudbeaver.server;visibility:=reexport
|
||||
Require-Bundle: io.cloudbeaver.server;visibility:=reexport,
|
||||
com.google.guava
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.model,
|
||||
io.cloudbeaver.model.config,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server.ce</artifactId>
|
||||
<version>25.1.4-SNAPSHOT</version>
|
||||
<version>25.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
extend type ServerConfig {
|
||||
serverURL: String!
|
||||
serverURL: String! @deprecated
|
||||
rootURI: String!
|
||||
hostName: String! @deprecated(reason: "use container id instead")
|
||||
containerId: String!
|
||||
@@ -12,5 +12,11 @@ extend type ServerConfig {
|
||||
redirectOnFederatedAuth: Boolean!
|
||||
enabledAuthProviders: [ID!]!
|
||||
passwordPolicyConfiguration: PasswordPolicyConfig! @since(version: "23.3.3")
|
||||
"Mark all cookies as secure, recommend for HTTPS deployment configurations"
|
||||
forceHttps: Boolean! @since(version: "25.1.3")
|
||||
"List of allowed hosts to use the application - if empty, all hosts are allowed"
|
||||
supportedHosts: [String!]! @since(version: "25.1.3")
|
||||
"Indicates whether the session should be linked to the user's IP address"
|
||||
bindSessionToIp: String! @since(version: "25.1.4")
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,8 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CBWebServerConfig extends WebServerConfig {
|
||||
private final CBApplication<?> cbApp;
|
||||
|
||||
@@ -86,4 +88,21 @@ public class CBWebServerConfig extends WebServerConfig {
|
||||
public PasswordPolicyConfiguration getPasswordPolicyConfiguration() {
|
||||
return cbApp.getServerConfiguration().getSecurityManagerConfiguration().getPasswordPolicyConfiguration();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isForceHttps() {
|
||||
return cbApp.getServerConfiguration().isForceHttps();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<String> getSupportedHosts() {
|
||||
return cbApp.getServerConfiguration().getSupportedHosts();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getBindSessionToIp() {
|
||||
return cbApp.getServerConfiguration().getBindSessionToIp();
|
||||
}
|
||||
}
|
||||
|
||||
+54
-7
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.config;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.cloudbeaver.auth.CBAuthConstants;
|
||||
import io.cloudbeaver.model.app.WebServerConfiguration;
|
||||
@@ -23,15 +24,17 @@ import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
public class CBServerConfig implements WebServerConfiguration {
|
||||
|
||||
private static final Log log = Log.getLog(CBServerConfig.class);
|
||||
|
||||
protected String serverURL;
|
||||
protected boolean secureCookies;
|
||||
@NotNull
|
||||
protected List<String> supportedHosts = new ArrayList<>();
|
||||
protected boolean forceHttps;
|
||||
protected int serverPort = CBConstants.DEFAULT_SERVER_PORT;
|
||||
private String serverHost = null;
|
||||
private String serverName = null;
|
||||
@@ -52,7 +55,8 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
@SerializedName("database")
|
||||
private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig();
|
||||
private String staticContent = "";
|
||||
private boolean bindSessionToIp = true;
|
||||
@NotNull
|
||||
private String bindSessionToIp = CBConstants.BIND_SESSION_DISABLE;
|
||||
|
||||
public CBServerConfig() {
|
||||
this.securityManagerConfiguration = createSecurityManagerConfiguration();
|
||||
@@ -184,11 +188,54 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
return new SMControllerConfiguration();
|
||||
}
|
||||
|
||||
public boolean isSecureCookies() {
|
||||
return secureCookies;
|
||||
public boolean isForceHttps() {
|
||||
return forceHttps;
|
||||
}
|
||||
|
||||
public boolean isBindSessionToIp() {
|
||||
public void setForceHttps(boolean forceHttps) {
|
||||
this.forceHttps = forceHttps;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getSupportedHosts() {
|
||||
return new ArrayList<>(supportedHosts);
|
||||
}
|
||||
|
||||
public void setSupportedHosts(@NotNull Collection<String> availableHosts) {
|
||||
LinkedHashSet<String> uniqueHosts = new LinkedHashSet<>();
|
||||
for (String host : availableHosts) {
|
||||
try {
|
||||
if (!host.startsWith("http://") && !host.startsWith("https://")) {
|
||||
host = "http://" + host; // Default to HTTP if no scheme is provided to avoid uri parse exception
|
||||
}
|
||||
URI uri = URI.create(host);
|
||||
String hostName = uri.getHost() != null ? uri.getHost() : host;
|
||||
if (InetAddresses.isInetAddress(hostName)) {
|
||||
log.warn("Host URI contains an IP address: " + hostName + ", skipped.");
|
||||
continue;
|
||||
}
|
||||
StringBuilder hostNameBuilder = new StringBuilder(hostName);
|
||||
|
||||
if (uri.getPort() > 0) {
|
||||
hostNameBuilder.append(':')
|
||||
.append(uri.getPort());
|
||||
}
|
||||
|
||||
uniqueHosts.add(hostNameBuilder.toString());
|
||||
} catch (Exception e) {
|
||||
log.error("Invalid host URI: " + host, e);
|
||||
}
|
||||
}
|
||||
this.supportedHosts.clear();
|
||||
this.supportedHosts.addAll(uniqueHosts);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getBindSessionToIp() {
|
||||
return bindSessionToIp;
|
||||
}
|
||||
|
||||
public void setBindSessionToIp(@NotNull String bindSessionToIp) {
|
||||
this.bindSessionToIp = bindSessionToIp;
|
||||
}
|
||||
}
|
||||
|
||||
+38
-12
@@ -27,6 +27,7 @@ import io.cloudbeaver.model.app.ServletSystemInformationCollector;
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.registry.WebDriverRegistry;
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.jetty.CBJettyServer;
|
||||
import io.cloudbeaver.service.ConnectionController;
|
||||
@@ -102,7 +103,6 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
// Persistence
|
||||
protected SMAdminController securityController;
|
||||
private boolean configurationMode = false;
|
||||
private String localHostAddress;
|
||||
protected String containerId;
|
||||
private final List<InetAddress> localInetAddresses = new ArrayList<>();
|
||||
|
||||
@@ -115,6 +115,8 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
|
||||
private CBJettyServer jettyServer;
|
||||
|
||||
private final Map<String, Object> applicationContext = new ConcurrentHashMap<>();
|
||||
|
||||
public CBApplication() {
|
||||
this.homeDirectory = new File(initHomeFolder());
|
||||
}
|
||||
@@ -214,19 +216,15 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
|
||||
configurationMode = CommonUtils.isEmpty(getServerConfiguration().getServerName());
|
||||
|
||||
refreshDisabledDriversConfig();
|
||||
try {
|
||||
refreshServerConfiguration();
|
||||
} catch (DBException e) {
|
||||
log.error("Error refreshing server configuration", e);
|
||||
return;
|
||||
}
|
||||
|
||||
eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on
|
||||
|
||||
// Determine address for local host
|
||||
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
|
||||
if (CommonUtils.isEmpty(localHostAddress)) {
|
||||
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
|
||||
}
|
||||
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
|
||||
localHostAddress)) {
|
||||
localHostAddress = CBConstants.HOST_LOCALHOST;
|
||||
}
|
||||
|
||||
Location instanceLoc = Platform.getInstanceLocation();
|
||||
try {
|
||||
@@ -331,6 +329,25 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
log.debug("Shutdown");
|
||||
}
|
||||
|
||||
private void refreshServerConfiguration() throws DBException {
|
||||
refreshDisabledDriversConfig();
|
||||
refreshEnabledFeatures();
|
||||
if (!isConfigurationMode()) {
|
||||
flushConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshEnabledFeatures() {
|
||||
Set<String> enabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getEnabledFeatures()));
|
||||
Set<String> disabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getDisabledFeatures()));
|
||||
|
||||
WebFeatureRegistry.getInstance().getWebFeatures().stream()
|
||||
.filter(f -> f.isEnabledByDefault() && !disabledFeatures.contains(f.getId()))
|
||||
.forEach(f -> enabledFeatures.add(f.getId()));
|
||||
|
||||
getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(new String[0]));
|
||||
}
|
||||
|
||||
protected ServletSystemInformationCollector<?> createSystemInformationCollector() {
|
||||
return new ServletSystemInformationCollector<>(this);
|
||||
}
|
||||
@@ -502,7 +519,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
}
|
||||
|
||||
public String getLocalHostAddress() {
|
||||
return localHostAddress;
|
||||
return getServerConfigurationController().getLocalHostAddress();
|
||||
}
|
||||
|
||||
public List<InetAddress> getLocalInetAddresses() {
|
||||
@@ -777,4 +794,13 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
|
||||
return systemInformationCollector;
|
||||
}
|
||||
|
||||
public void addApplicationContextValue(@NotNull String key, @NotNull Object value) {
|
||||
applicationContext.put(key, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getApplicationContextValue(@NotNull String key) {
|
||||
return (T) applicationContext.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
+33
-11
@@ -44,8 +44,6 @@ import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -66,6 +64,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
protected final Path homeDirectory;
|
||||
private final Map<String, String> externalProperties = new LinkedHashMap<>();
|
||||
private final Map<String, Object> originalConfigurationProperties = new LinkedHashMap<>();
|
||||
private String localHostAddress;
|
||||
|
||||
protected CBServerConfigurationController(@NotNull T serverConfiguration, @NotNull Path homeDirectory) {
|
||||
super(homeDirectory);
|
||||
@@ -80,6 +79,15 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
@Override
|
||||
public void loadServerConfiguration(Path configPath) throws DBException {
|
||||
log.debug("Using configuration [" + configPath + "]");
|
||||
// Determine address for local host
|
||||
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
|
||||
if (CommonUtils.isEmpty(localHostAddress)) {
|
||||
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
|
||||
}
|
||||
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
|
||||
localHostAddress)) {
|
||||
localHostAddress = CBConstants.HOST_LOCALHOST;
|
||||
}
|
||||
|
||||
if (!Files.exists(configPath)) {
|
||||
log.error("Configuration file " + configPath + " doesn't exist. Use defaults.");
|
||||
@@ -93,6 +101,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
log.debug("Runtime configuration [" + runtimeConfigPath.toAbsolutePath() + "]");
|
||||
loadConfiguration(runtimeConfigPath);
|
||||
}
|
||||
|
||||
// Set default preferences
|
||||
PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(),
|
||||
ModelPreferences.UI_DRIVERS_HOME,
|
||||
@@ -107,7 +116,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
return CommonUtils.nullIfEmpty(envValue);
|
||||
}
|
||||
|
||||
public void loadConfiguration(Path configPath) throws DBException {
|
||||
@NotNull
|
||||
public Map<String, Object> loadConfiguration(Path configPath) throws DBException {
|
||||
CBAppConfig prevConfig = new CBAppConfig(appConfiguration);
|
||||
Map<String, Object> configProps = readConfiguration(configPath);
|
||||
try {
|
||||
@@ -123,6 +133,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
mergeOldConfiguration(prevConfig);
|
||||
|
||||
patchConfigurationWithProperties(getServerConfiguration().getProductSettings());
|
||||
return configProps;
|
||||
}
|
||||
|
||||
protected void parseConfiguration(Map<String, Object> configProps) throws DBException {
|
||||
@@ -159,12 +170,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
if (config.getServerURL() == null) {
|
||||
String hostName = config.getServerHost();
|
||||
if (CommonUtils.isEmpty(hostName)) {
|
||||
try {
|
||||
hostName = InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
log.debug("Error resolving localhost address: " + e.getMessage());
|
||||
hostName = CBConstants.HOST_LOCALHOST;
|
||||
}
|
||||
hostName = getLocalHostAddress();
|
||||
}
|
||||
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
|
||||
}
|
||||
@@ -531,8 +537,20 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SECURE_COOKIES,
|
||||
serverConfig.isSecureCookies()
|
||||
CBConstants.PARAM_FORCE_HTTPS,
|
||||
serverConfig.isForceHttps()
|
||||
);
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SUPPORTED_HOSTS,
|
||||
serverConfig.getSupportedHosts()
|
||||
);
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_BIND_SESSION_TO_IP,
|
||||
serverConfig.getBindSessionToIp()
|
||||
);
|
||||
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
|
||||
@@ -654,4 +672,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
public void validateFinalServerConfiguration() throws DBException {
|
||||
|
||||
}
|
||||
|
||||
public String getLocalHostAddress() {
|
||||
return localHostAddress;
|
||||
}
|
||||
}
|
||||
+5
-8
@@ -54,18 +54,15 @@ public class WSSubjectPermissionUpdatedEventHandler extends WSDefaultEventHandle
|
||||
if (!super.isAcceptableInSession(activeUserSession, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var user = activeUserSession.getUserContext().getUser();
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
var subjectId = event.getSubjectId();
|
||||
switch (event.getSubjectType()) {
|
||||
case user:
|
||||
return CommonUtils.equalObjects(user.getUserId(), subjectId);
|
||||
case team:
|
||||
return ArrayUtils.containsIgnoreCase(user.getTeams(), subjectId);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return switch (event.getSubjectType()) {
|
||||
case user -> CommonUtils.equalObjects(user.getUserId(), subjectId);
|
||||
case team -> ArrayUtils.containsIgnoreCase(user.getTeams(), subjectId);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+13
-18
@@ -22,29 +22,24 @@ import org.jkiss.dbeaver.model.websocket.WSEventHandler;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSUserCloseSessionsEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSUserDisabledEvent;
|
||||
|
||||
public class WSUserEventHandler<EVENT extends WSAbstractEvent> implements WSEventHandler<EVENT> {
|
||||
@Override
|
||||
public void handleEvent(@NotNull EVENT event) {
|
||||
var sessionManager = CBApplication.getInstance().getSessionManager();
|
||||
switch (event.getId()) {
|
||||
case WSUserCloseSessionsEvent.ID:
|
||||
if (event instanceof WSUserCloseSessionsEvent closeSessionsEvent) {
|
||||
if (closeSessionsEvent.getSessionIds().isEmpty()) {
|
||||
sessionManager.closeAllSessions(closeSessionsEvent.getSessionId());
|
||||
} else {
|
||||
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WSUserDeletedEvent.ID:
|
||||
if (event instanceof WSUserDeletedEvent userDeletedEvent) {
|
||||
sessionManager.closeUserSession(userDeletedEvent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case WSUserCloseSessionsEvent closeSessionsEvent -> {
|
||||
if (closeSessionsEvent.getSessionIds().isEmpty()) {
|
||||
sessionManager.closeAllSessions(closeSessionsEvent.getSessionId());
|
||||
} else {
|
||||
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
|
||||
}
|
||||
}
|
||||
case WSUserDeletedEvent e -> sessionManager.closeUserSession(e);
|
||||
case WSUserDisabledEvent e -> sessionManager.closeUserSession(e);
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-9
@@ -16,12 +16,13 @@
|
||||
*/
|
||||
package io.cloudbeaver.server.jetty;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
|
||||
import io.cloudbeaver.server.filters.ServerConfigurationTimeLimitFilter;
|
||||
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
|
||||
import io.cloudbeaver.server.servlets.CBImageServlet;
|
||||
import io.cloudbeaver.server.servlets.CBStaticServlet;
|
||||
import io.cloudbeaver.server.servlets.WebStatusServlet;
|
||||
@@ -30,10 +31,7 @@ import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
|
||||
import io.cloudbeaver.service.DBWServiceBindingServlet;
|
||||
import io.cloudbeaver.service.DBWServiceBindingWebSocket;
|
||||
import jakarta.websocket.server.ServerEndpointConfig;
|
||||
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletMapping;
|
||||
import org.eclipse.jetty.ee10.servlet.*;
|
||||
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
|
||||
import org.eclipse.jetty.server.*;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
@@ -48,6 +46,8 @@ import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class CBJettyServer {
|
||||
|
||||
@@ -92,6 +92,7 @@ public class CBJettyServer {
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
// Handler configuration
|
||||
Path contentRootPath = Path.of(serverConfiguration.getContentRoot());
|
||||
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
@@ -99,6 +100,7 @@ public class CBJettyServer {
|
||||
String rootURI = serverConfiguration.getRootURI();
|
||||
servletContextHandler.setContextPath(rootURI);
|
||||
|
||||
|
||||
ServletHolder staticServletHolder = new ServletHolder(
|
||||
"static", new CBStaticServlet(Path.of(serverConfiguration.getContentRoot()))
|
||||
);
|
||||
@@ -116,17 +118,20 @@ public class CBJettyServer {
|
||||
|
||||
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
|
||||
|
||||
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
|
||||
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
|
||||
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
|
||||
servletContextHandler.addServlet(
|
||||
new ServletHolder(
|
||||
"graphql",
|
||||
new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application))
|
||||
endpoint
|
||||
),
|
||||
serverConfiguration.getServicesURI() + "gql/*"
|
||||
gqlServletPath
|
||||
);
|
||||
servletContextHandler.addEventListener(new CBServerContextListener(application));
|
||||
|
||||
// Add extensions from services
|
||||
|
||||
Set<String> excludedFilterPaths = new HashSet<>();
|
||||
CBJettyServletContext servletContext = new CBJettyServletContext(servletContextHandler);
|
||||
for (DBWServiceBindingServlet wsd : WebServiceRegistry.getInstance()
|
||||
.getWebServices(DBWServiceBindingServlet.class)
|
||||
@@ -134,6 +139,7 @@ public class CBJettyServer {
|
||||
if (wsd.isApplicable(this.application)) {
|
||||
try {
|
||||
wsd.addServlets(this.application, servletContext);
|
||||
excludedFilterPaths.addAll(wsd.getExcludedServletPaths(this.application));
|
||||
} catch (DBException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -152,6 +158,13 @@ public class CBJettyServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(
|
||||
application,
|
||||
excludedFilterPaths,
|
||||
Set.of(gqlServletPath)
|
||||
));
|
||||
servletContextHandler.addFilter(hostsFilter, "/*", null);
|
||||
|
||||
|
||||
JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) -> {
|
||||
// Add echo endpoint to server container
|
||||
@@ -231,7 +244,7 @@ public class CBJettyServer {
|
||||
&& servletContextHandler.getSessionHandler() instanceof CBSessionHandler cbSessionHandler
|
||||
) {
|
||||
cbSessionHandler.setMaxCookieAge((int) (application.getMaxSessionIdleTime() / 1000));
|
||||
cbSessionHandler.setSecureCookies(application.getServerConfiguration().isSecureCookies());
|
||||
cbSessionHandler.setSecureCookies(application.getServerConfiguration().isForceHttps());
|
||||
}
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.server.jetty;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class RequestHostFilter implements Filter {
|
||||
private static final Log log = Log.getLog(RequestHostFilter.class);
|
||||
|
||||
@NotNull
|
||||
private final CBApplication<?> application;
|
||||
private final Set<String> excludedPaths = new HashSet<>();
|
||||
private final Set<String> errorPaths = new HashSet<>();
|
||||
|
||||
public RequestHostFilter(
|
||||
@NotNull CBApplication<?> application,
|
||||
@NotNull Set<String> excludedPaths,
|
||||
@NotNull Set<String> errorPaths
|
||||
) {
|
||||
this.application = application;
|
||||
this.excludedPaths.addAll(
|
||||
excludedPaths.stream()
|
||||
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
|
||||
.toList()
|
||||
);
|
||||
this.errorPaths.addAll(
|
||||
errorPaths.stream()
|
||||
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
boolean requestAllowed = true;
|
||||
|
||||
if (request instanceof HttpServletRequest httpRequest) {
|
||||
CBServerConfig serverConfig = application.getServerConfiguration();
|
||||
URI originUri;
|
||||
try {
|
||||
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
|
||||
originUri = URI.create(origin);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get origin from request", e);
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
|
||||
String servletPath = httpRequest.getServletPath();
|
||||
requestAllowed = isIpAddress;
|
||||
if (!requestAllowed) {
|
||||
if (CommonUtils.isNotEmpty(servletPath)) {
|
||||
for (String excludedPath : excludedPaths) {
|
||||
if (servletPath.contains(excludedPath)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
requestAllowed = validateHosts(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
|
||||
}
|
||||
if (requestAllowed) {
|
||||
requestAllowed = validateSchema(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
|
||||
}
|
||||
}
|
||||
if (requestAllowed) {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateSchema(
|
||||
@NotNull CBServerConfig serverConfig,
|
||||
@NotNull HttpServletRequest httpRequest,
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull URI originUri
|
||||
) {
|
||||
boolean httpsExpected = serverConfig.isForceHttps();
|
||||
try {
|
||||
if ("http".equals(originUri.getScheme()) && httpsExpected) {
|
||||
log.warn("Request schema is 'http' but 'forceHttps' is enabled. Redirecting to 'https'.");
|
||||
StringBuilder redirectUrlBuilder = new StringBuilder("https://")
|
||||
.append(originUri.getHost());
|
||||
if (originUri.getPort() > -1) {
|
||||
redirectUrlBuilder.append(':').append(originUri.getPort());
|
||||
}
|
||||
redirectUrlBuilder.append(httpRequest.getRequestURI());
|
||||
if (httpRequest.getQueryString() != null) {
|
||||
redirectUrlBuilder.append("?")
|
||||
.append(httpRequest.getQueryString());
|
||||
}
|
||||
response.sendRedirect(redirectUrlBuilder.toString());
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to redirect to HTTPS", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateHosts(
|
||||
@NotNull CBServerConfig serverConfig,
|
||||
@NotNull HttpServletRequest httpRequest,
|
||||
@NotNull HttpServletResponse response,
|
||||
URI originUri
|
||||
) throws IOException {
|
||||
List<String> availableHosts = serverConfig.getSupportedHosts();
|
||||
if (CommonUtils.isEmpty(availableHosts)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
var requestHostBuilder = new StringBuilder(originUri.getHost());
|
||||
if (originUri.getPort() > -1) {
|
||||
requestHostBuilder.append(':').append(originUri.getPort());
|
||||
}
|
||||
String requestHost = requestHostBuilder.toString();
|
||||
if (!availableHosts.contains(requestHost)) {
|
||||
for (String errorPath : errorPaths) {
|
||||
if (httpRequest.getServletPath().contains(errorPath)) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.getWriter().write(
|
||||
"Request host '" + requestHost + "' is not allowed. Available hosts: " + availableHosts
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
log.warn("Request host '" + requestHost + "' is not allowed. Redirect to default: " + availableHosts);
|
||||
redirectToDefaultHost(response, httpRequest, availableHosts);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error(e.getMessage(), e);
|
||||
redirectToDefaultHost(response, httpRequest, availableHosts);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void redirectToDefaultHost(
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull HttpServletRequest httpRequest,
|
||||
@NotNull List<String> availableHosts
|
||||
) throws IOException {
|
||||
boolean https = application.getServerConfiguration().isForceHttps();
|
||||
String redirectUrl = (https ? "https://" : "http://") + getDefaultHost(availableHosts) + httpRequest.getRequestURI();
|
||||
if (httpRequest.getQueryString() != null) {
|
||||
redirectUrl += "?" + httpRequest.getQueryString();
|
||||
}
|
||||
response.sendRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getDefaultHost(@NotNull List<String> availableHosts) {
|
||||
return availableHosts.getFirst();
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -22,6 +22,7 @@ import io.cloudbeaver.model.session.*;
|
||||
import io.cloudbeaver.registry.WebHandlerRegistry;
|
||||
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.WebAppSessionManager;
|
||||
import io.cloudbeaver.server.events.WSWebUtils;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
@@ -34,7 +35,9 @@ import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
|
||||
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSUserDisabledEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionStateEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -156,8 +159,15 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
validateSessionIp(request, webSession);
|
||||
|
||||
return webSession;
|
||||
}
|
||||
|
||||
private void validateSessionIp(@NotNull HttpServletRequest request, WebSession webSession) {
|
||||
boolean bindingEnabled = isSessionBindingEnabled(request);
|
||||
String currentRemote = request.getRemoteAddr();
|
||||
if (application.getServerConfiguration().isBindSessionToIp()
|
||||
if (bindingEnabled
|
||||
&& (CommonUtils.isEmpty(currentRemote) || !currentRemote.equals(webSession.getLastRemoteAddr()))
|
||||
) {
|
||||
var error = new DBWebException(
|
||||
@@ -169,8 +179,11 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
webSession.addSessionError(error);
|
||||
closeSession(webSession.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
return webSession;
|
||||
protected boolean isSessionBindingEnabled(@NotNull HttpServletRequest request) {
|
||||
String bindingState = application.getServerConfiguration().getBindSessionToIp();
|
||||
return CBConstants.BIND_SESSION_ENABLE.equalsIgnoreCase(bindingState) || Boolean.parseBoolean(bindingState);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -391,14 +404,14 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void closeUserSession(@NotNull WSUserDeletedEvent userDeletedEvent) {
|
||||
public void closeUserSession(@NotNull WSAbstractEvent event) {
|
||||
synchronized (sessionMap) {
|
||||
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
|
||||
var session = iterator.next();
|
||||
if (CommonUtils.equalObjects(session.getUserContext().getUserId(),
|
||||
userDeletedEvent.getDeletedUserId())) {
|
||||
event.getUserId())) {
|
||||
if (session instanceof WebHeadlessSession headlessSession) {
|
||||
headlessSession.addSessionEvent(userDeletedEvent);
|
||||
headlessSession.addSessionEvent(event);
|
||||
}
|
||||
iterator.remove();
|
||||
session.close();
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 25.1.4.qualifier
|
||||
Bundle-Release-Date: 20250804
|
||||
Bundle-Version: 25.2.1.qualifier
|
||||
Bundle-Release-Date: 20250922
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
log.api.graphql.debug = Log GraphQL requests to console
|
||||
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
|
||||
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
log.api.graphql.debug =Логировать GraphQL-запросы в консоль
|
||||
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
|
||||
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>25.1.4-SNAPSHOT</version>
|
||||
<version>25.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -351,6 +351,9 @@ type DriverInfo {
|
||||
"""
|
||||
providerProperties: [ObjectPropertyInfo!]!
|
||||
|
||||
"Expert driver settings properties. Returns properties (like keep-alive interval) that are not often used and can be hidden in UI"
|
||||
expertSettingsProperties: [ObjectPropertyInfo!]! @since(version: "25.2.1")
|
||||
|
||||
"False for drivers which do not support authentication."
|
||||
anonymousAccess: Boolean
|
||||
"Default auth model that is used for this driver (see authModels)"
|
||||
@@ -486,6 +489,9 @@ type ConnectionInfo {
|
||||
"Main connection properties. Contains host, port, database, server name fields"
|
||||
mainPropertyValues: Object @since(version: "24.1.2")
|
||||
|
||||
"Expert connection settings. Contains expert settings properties (like keep-alive interval or auto-commit) that are not often used"
|
||||
expertSettingsValues: Object @since(version: "25.2.1")
|
||||
|
||||
"Connection keep-alive interval in seconds"
|
||||
keepAliveInterval: Int!
|
||||
"Defines if the connection is in auto-commit mode"
|
||||
@@ -672,6 +678,9 @@ input ConnectionConfig {
|
||||
"Host, port, serverName, databaseName are also stored in mainPropertyValues for custom pages"
|
||||
mainPropertyValues: Object @since(version: "24.1.2")
|
||||
|
||||
"Keep-alive, auto-commit, read-only and other expert settings are stored in expertSettingsValues"
|
||||
expertSettingsValues: Object @since(version: "25.2.1")
|
||||
|
||||
"Sets connection URL jdbc:{driver}://{host}[:{port}]/[{database}]"
|
||||
url: String
|
||||
|
||||
@@ -679,13 +688,13 @@ input ConnectionConfig {
|
||||
properties: Object
|
||||
|
||||
"Set keep-alive interval"
|
||||
keepAliveInterval: Int
|
||||
keepAliveInterval: Int @deprecated(reason: "25.2.1 use expertPropertyValues instead")
|
||||
|
||||
"Sets auto-commit connection state"
|
||||
autocommit: Boolean
|
||||
autocommit: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
|
||||
|
||||
"Sets read-only connection state"
|
||||
readOnly: Boolean
|
||||
readOnly: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
|
||||
|
||||
# User credentials
|
||||
|
||||
@@ -721,9 +730,9 @@ input ConnectionConfig {
|
||||
"Configuration type (MANUAL, URL)"
|
||||
configurationType: DriverConfigurationType
|
||||
"Sets catalog name for the connection"
|
||||
defaultCatalogName: String @since(version: "25.0.5")
|
||||
defaultCatalogName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
|
||||
"Sets schema name for the connection"
|
||||
defaultSchemaName: String @since(version: "25.0.5")
|
||||
defaultSchemaName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
|
||||
}
|
||||
|
||||
####################################################
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
@@ -318,9 +317,11 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
return container.getName() + " [" + container.getId() + "]";
|
||||
}
|
||||
|
||||
public static void updateConfigAndRefreshDatabases(WebSession session, String projectId) throws DBWebException {
|
||||
public static void refreshDatabases(WebSession session, String projectId) throws DBWebException {
|
||||
DBNProject projectNode = session.getNavigatorModelOrThrow().getRoot().getProjectNode(session.getProjectById(projectId));
|
||||
DBNModel.updateConfigAndRefreshDatabases(projectNode.getDatabases());
|
||||
if (projectNode != null) {
|
||||
projectNode.getDatabases().refreshChildren();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGlobalProject(DBPProject project) {
|
||||
@@ -363,7 +364,7 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
@NotNull
|
||||
public static WebPropertyInfo[] getObjectFilteredProperties(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBPObject object,
|
||||
@NotNull Object object,
|
||||
@Nullable WebPropertyFilter filter
|
||||
) {
|
||||
PropertyCollector propertyCollector = new PropertyCollector(object, true);
|
||||
|
||||
+5
-21
@@ -18,10 +18,7 @@ package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.app.WebAppConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.utils.ConfigurationUtils;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
@@ -39,8 +36,6 @@ import org.jkiss.utils.CommonUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Web driver configuration
|
||||
@@ -257,29 +252,18 @@ public class WebDatabaseDriverInfo {
|
||||
|
||||
@Property
|
||||
public WebPropertyInfo[] getProviderProperties() {
|
||||
WebPropertyInfo[] additionalWebProperty = Optional.of(WebAppUtils.getWebApplication())
|
||||
.filter(app -> app.getAppConfiguration().isSecretManagerEnabled())
|
||||
.map(app -> app.getConnectionController().getExternalInfo(webSession))
|
||||
.orElse(new WebPropertyInfo[0]);
|
||||
|
||||
WebPropertyInfo[] providerProperties = Arrays.stream(driver.getProviderPropertyDescriptors())
|
||||
return Arrays.stream(driver.getProviderPropertyDescriptors())
|
||||
.map(p -> new WebPropertyInfo(webSession, p, null))
|
||||
.toArray(WebPropertyInfo[]::new);
|
||||
}
|
||||
|
||||
return Stream.concat(
|
||||
Arrays.stream(additionalWebProperty),
|
||||
Arrays.stream(providerProperties)
|
||||
).toArray(WebPropertyInfo[]::new);
|
||||
public WebPropertyInfo[] getExpertSettingsProperties() {
|
||||
return WebServiceUtils.getObjectFilteredProperties(webSession, new WebExpertSettingsProperties(driver), null);
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isEnabled() {
|
||||
WebAppConfiguration config = WebAppUtils.getWebApplication().getAppConfiguration();
|
||||
return ConfigurationUtils.isDriverEnabled(
|
||||
driver,
|
||||
config.getEnabledDrivers(),
|
||||
config.getDisabledDrivers()
|
||||
);
|
||||
return WebServiceUtils.isDriverEnabled(driver);
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -41,7 +41,7 @@ import java.util.Map;
|
||||
public class WebServerConfig {
|
||||
|
||||
private final WebApplication application;
|
||||
private boolean provideSensitiveInformation = true;
|
||||
protected boolean provideSensitiveInformation = true;
|
||||
|
||||
public WebServerConfig(@NotNull WebApplication application) {
|
||||
this.application = application;
|
||||
@@ -136,7 +136,7 @@ public class WebServerConfig {
|
||||
|
||||
@Property
|
||||
public WebServerLanguage[] getSupportedLanguages() {
|
||||
List<PlatformLanguageDescriptor> langs = PlatformLanguageRegistry.getInstance().getLanguages();
|
||||
List<PlatformLanguageDescriptor> langs = new ArrayList<>(PlatformLanguageRegistry.getInstance().getLanguages());
|
||||
// FIXME: remove hardcoded ordering
|
||||
langs.sort(Comparator.comparingInt(x -> x.getCode().equals("vi") ? 1 : -1));
|
||||
WebServerLanguage[] webLangs = new WebServerLanguage[langs.size()];
|
||||
|
||||
+1
-4
@@ -39,10 +39,7 @@ public class WSDataSourceUpdatedEventHandlerImpl extends WSAbstractProjectEventH
|
||||
log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId());
|
||||
return;
|
||||
}
|
||||
sendEvent = project.updateProjectDataSources(
|
||||
event.getDataSourceIds(),
|
||||
event.getId()
|
||||
);
|
||||
sendEvent = project.updateProjectDataSources(event);
|
||||
}
|
||||
if (sendEvent) {
|
||||
activeUserSession.addSessionEvent(event);
|
||||
|
||||
+34
-21
@@ -38,10 +38,11 @@ import io.cloudbeaver.service.DBWBindingContext;
|
||||
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -73,7 +74,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
public static final String API_PROTOCOL = "GraphQL";
|
||||
private final GraphQL graphQL;
|
||||
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
public static final Gson gson = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
@@ -143,7 +144,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String contentType = request.getContentType();
|
||||
if (CommonUtils.isEmpty(contentType) || !contentType.startsWith(HttpConstants.TYPE_JSON)) {
|
||||
String error = "Bad request," + (CommonUtils.isEmpty(contentType)
|
||||
@@ -224,11 +225,11 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
}
|
||||
|
||||
private void executeQuery(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
String query,
|
||||
Map<String, Object> variables,
|
||||
String operationName
|
||||
@NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull String query,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@Nullable String operationName
|
||||
) throws IOException {
|
||||
Map<String, Object> mapOfContext =
|
||||
Map.of(
|
||||
@@ -270,15 +271,23 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
errorMessage = executionException.getMessage();
|
||||
}
|
||||
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
|
||||
apiCallInterceptor.onApiCallEvent(request, variables, operationName, startTime, errorMessage, API_PROTOCOL);
|
||||
apiCallInterceptor.onApiCallEvent(
|
||||
request,
|
||||
variables,
|
||||
CommonUtils.notEmpty(operationName), userId, startTime,
|
||||
errorMessage,
|
||||
API_PROTOCOL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> resJSON = executionResult.toSpecification();
|
||||
String resString = gson.toJson(resJSON);
|
||||
setDevelHeaders(request, response);
|
||||
response.setContentType(GraphQLConstants.CONTENT_TYPE_JSON_UTF8);
|
||||
response.getWriter().print(resString);
|
||||
if (executionResult != null) {
|
||||
Map<String, Object> resJSON = executionResult.toSpecification();
|
||||
String resString = gson.toJson(resJSON);
|
||||
setDevelHeaders(request, response);
|
||||
response.setContentType(GraphQLConstants.CONTENT_TYPE_JSON_UTF8);
|
||||
response.getWriter().print(resString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -296,8 +305,8 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (exception instanceof GraphQLException && exception.getCause() != null) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception instanceof InvocationTargetException) {
|
||||
exception = ((InvocationTargetException) exception).getTargetException();
|
||||
if (exception instanceof InvocationTargetException ite) {
|
||||
exception = ite.getTargetException();
|
||||
}
|
||||
log.debug(
|
||||
"GraphQL call failed at '" + handlerParameters.getPath() + "'" /*+ ", " + handlerParameters.getArgumentValues()*/,
|
||||
@@ -317,17 +326,17 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (!(exception instanceof GraphQLError)) {
|
||||
exception = new DBWebException(exception.getMessage(), exception);
|
||||
}
|
||||
if (exception instanceof DBWebException) {
|
||||
((DBWebException) exception).setPath(path.toList());
|
||||
((DBWebException) exception).setLocations(Collections.singletonList(sourceLocation));
|
||||
if (exception instanceof DBWebException webException) {
|
||||
webException.setPath(path.toList());
|
||||
webException.setLocations(Collections.singletonList(sourceLocation));
|
||||
}
|
||||
var result = handlerResult.error((GraphQLError) exception).build();
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static HttpServletRequest getServletRequest(DataFetchingEnvironment env) {
|
||||
@NotNull
|
||||
public static HttpServletRequest getServletRequestOrThrow(DataFetchingEnvironment env) {
|
||||
GraphQLContext context = env.getGraphQlContext();
|
||||
HttpServletRequest request = context.get("request");
|
||||
if (request == null) {
|
||||
@@ -350,4 +359,8 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
return context.get("bindingContext");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GraphQL getGraphQL() {
|
||||
return graphQL;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@ import jakarta.websocket.HandshakeResponse;
|
||||
import jakarta.websocket.server.HandshakeRequest;
|
||||
import jakarta.websocket.server.ServerEndpointConfig;
|
||||
import org.eclipse.jetty.ee10.websocket.jakarta.server.internal.JakartaWebSocketCreator;
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -88,7 +89,7 @@ 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");
|
||||
throw new BadMessageException("No web session found for websocket request");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -21,11 +21,9 @@ import io.cloudbeaver.WebObjectId;
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
@@ -98,5 +96,4 @@ public interface ConnectionController {
|
||||
|
||||
void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException;
|
||||
|
||||
WebPropertyInfo[] getExternalInfo(WebSession webSession);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user