Compare commits

..
3 Commits
Author SHA1 Message Date
serge-rider 8155d328a2 Revert "CB-4603 Set global default user team ID"
This reverts commit 6118e706e0.
2024-04-03 12:05:00 +02:00
serge-rider 6118e706e0 CB-4603 Set global default user team ID 2024-04-03 10:35:52 +02:00
serge-rider eab07b9e9f CB-4603 Set global default user team ID 2024-04-03 10:28:36 +02:00
3256 changed files with 32178 additions and 46413 deletions
+35
View File
@@ -0,0 +1,35 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:1-17-bookworm",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installMaven": "true",
"installGradle": "false"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {}
},
"hostRequirements": {
"cpus": 8,
"memory": "16gb",
"storage": "64gb"
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
-56
View File
@@ -1,56 +0,0 @@
name: Backend Build
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
jobs:
build-backend:
name: Backend
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
cache: maven
- uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.0
- name: Give permissions
run: |
sudo chmod 777 ../
shell: bash
- name: Determine branches
id: determine-branch
run: |
echo "pr_branch=${{ github.head_ref }}" >> $GITHUB_ENV
echo "base_branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_ENV
- name: Clone dbeaver/dbeaver
id: clone-repo
run: |
git clone -b ${{ env.pr_branch }} https://github.com/dbeaver/dbeaver.git ../dbeaver || git clone -b ${{ env.base_branch }} https://github.com/dbeaver/dbeaver.git ../dbeaver
- name: Run build script
run: ./build-backend.sh
shell: bash
working-directory: ./deploy
- name: Archive build artifacts
uses: actions/upload-artifact@v4
with:
name: backend-build-artifacts
path: deploy/cloudbeaver
if-no-files-found: error
-40
View File
@@ -1,40 +0,0 @@
name: Backend Lint
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
inputs:
skip_cache:
required: false
type: string
jobs:
lint:
name: Backend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout checkstyle config repository
uses: actions/checkout@v4
with:
repository: dbeaver/dbeaver
path: dbeaver-config
- name: Copy checkstyle config
run: cp dbeaver-config/dbeaver-checkstyle-config.xml ./dbeaver-checkstyle-config.xml
- name: Remove checkstyle config directory
run: rm -rf dbeaver-config
- uses: dbelyaev/action-checkstyle@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
filter_mode: diff_context
checkstyle_config: ./dbeaver-checkstyle-config.xml
fail_on_error: true
-57
View File
@@ -1,57 +0,0 @@
name: Cleanup checks
on:
pull_request:
types: [closed]
jobs:
delete-caches:
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
# delete-docker-image:
# name: Delete Docker Image
# if: github.event.pull_request.merged == true
# runs-on: ubuntu-latest
# steps:
# - name: Check out the repository
# uses: actions/checkout@v4
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Determine Docker Image Tag
# run: |
# REPO_NAME=$(basename ${{ github.repository }})
# IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/$REPO_NAME
# BRANCH_NAME=${{ github.event.pull_request.head.ref }}
# TAG_NAME=$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9._-]/-/g')
# echo "image=$IMAGE_NAME:$TAG_NAME" >> $GITHUB_ENV
# - name: Log in to GitHub Container Registry
# run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
# - name: Delete Docker Image
# run: |
# docker rmi ${{ env.image }}
# echo "Deleted image: ${{ env.image }}"
-56
View File
@@ -1,56 +0,0 @@
name: Check
on:
push:
branches:
- devel
pull_request:
branches:
- devel
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
inputs:
skip_cache:
description: "Skip cache restoration"
required: false
default: "false"
jobs:
call-backend-build:
name: Build
uses: ./.github/workflows/backend-build.yml
call-frontend-build:
name: Build
uses: ./.github/workflows/frontend-build.yml
with:
skip_cache: ${{ github.event.inputs.skip_cache }}
call-frontend-lint:
name: Lint
needs: call-frontend-build
uses: ./.github/workflows/frontend-lint.yml
call-backend-lint:
name: Lint
uses: ./.github/workflows/backend-lint.yml
# call-frontend-tests:
# name: Frontend Unit Tests
# needs: call-frontend-build
# runs-on: ubuntu-latest
# steps:
# - name: Check if tests passed
# if: ${{ needs.call-frontend-build.outputs.test-status != 'success' }}
# run: |
# echo "Tests failed"
# exit 1
# - name: Continue if tests passed
# if: ${{ needs.call-frontend-build.outputs.test-status == 'success' }}
# run: echo "Tests passed"
# call-docker-build-and-push:
# name: Run
# needs: [call-backend-build, call-frontend-build]
# uses: ./.github/workflows/docker-build-and-push.yml
@@ -1,93 +0,0 @@
name: Build and Push Docker Image
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
jobs:
build-and-push-docker:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download backend artifacts
uses: actions/download-artifact@v4
with:
name: backend-build-artifacts
path: deploy/cloudbeaver/
- name: Download frontend artifacts
uses: actions/download-artifact@v4
with:
name: frontend-build-artifacts
path: deploy/cloudbeaver/web
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run custom Docker build script
run: ./make-docker-container.sh
shell: bash
working-directory: ./deploy/docker
- name: Tag Docker Image
run: |
REPO_NAME=$(basename ${{ github.repository }})
IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/$REPO_NAME
BRANCH_NAME=${{ github.head_ref || github.ref_name }}
TAG_NAME=$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9._-]/-/g')
docker tag dbeaver/cloudbeaver:dev $IMAGE_NAME:$TAG_NAME
echo "image=$IMAGE_NAME:$TAG_NAME" >> $GITHUB_ENV
# - name: Install Docker Credential Helper
# run: |
# sudo apt-get update
# sudo apt-get install -y gnupg2 pass
# curl -fsSL https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-pass-v0.6.4-amd64.tar.gz -o docker-credential-pass.tar.gz
# tar xzvf docker-credential-pass.tar.gz
# sudo mv docker-credential-pass /usr/local/bin/docker-credential-pass
# sudo chmod +x /usr/local/bin/docker-credential-pass
# - name: Configure Docker to use Credential Helper
# run: |
# mkdir -p ~/.docker
# echo '{"credsStore":"pass"}' > ~/.docker/config.json
# - name: Initialize Password Store
# run: |
# gpg --batch --gen-key <<EOF
# %no-protection
# Key-Type: default
# Subkey-Type: default
# Name-Real: Docker Credential Helper
# Expire-Date: 0
# %commit
# EOF
# pass init "$(gpg --list-keys --with-colons | grep '^pub' | cut -d: -f5)"
- name: Log in to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
- name: Push Docker Image
run: docker push ${{ env.image }}
# - name: Comment on Pull Request
# if: github.event_name == 'pull_request'
# uses: actions/github-script@v6
# with:
# github-token: ${{ secrets.PAT }}
# script: |
# const tag = process.env.TAG_NAME;
# const commentBody = `The Docker image has been built and pushed to Docker Hub.\n\nYou can pull the image using the following command:\n\`\`\`\ndocker pull dbeaver/cloudbeaver:${tag}\n\`\`\``;
# github.rest.issues.createComment({
# owner: context.repo.owner,
# repo: context.repo.repo,
# issue_number: context.issue.number,
# body: commentBody
# });
-91
View File
@@ -1,91 +0,0 @@
name: Frontend Build
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
outputs:
build-status:
description: "Build status"
value: ${{ jobs.frontend-build.outputs.build-status }}
test-status:
description: "Test status"
value: ${{ jobs.frontend-build.outputs.test-status }}
inputs:
skip_cache:
required: false
type: string
jobs:
frontend-build:
name: Frontend
runs-on: ubuntu-latest
outputs:
build-status: ${{ steps.build.outcome }}
test-status: ${{ steps.test.outcome }}
defaults:
run:
working-directory: ./webapp
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: yarn
cache-dependency-path: webapp/yarn.lock
- name: restore node_modules
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.os }}-node_modules-${{ hashFiles('webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-
- name: restore typescript cache
uses: actions/cache@v4
with:
path: "**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
- name: yarn clean
if: env.skip_cache == 'true'
uses: borales/actions-yarn@v5
with:
dir: webapp
cmd: clean
- name: yarn install --frozen-lockfile
uses: borales/actions-yarn@v5
with:
dir: webapp
cmd: install
- name: build
id: build
uses: borales/actions-yarn@v5
with:
dir: webapp/packages/product-default
cmd: bundle
- name: test
id: test
uses: borales/actions-yarn@v5
with:
dir: webapp
cmd: test
- name: Archive build artifacts
uses: actions/upload-artifact@v4
with:
name: frontend-build-artifacts
path: webapp/packages/product-default/lib
if-no-files-found: error
+76
View File
@@ -0,0 +1,76 @@
name: frontend-cache
on:
push:
branches: ["devel"]
paths:
- "webapp/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build_and_test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./webapp
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: restore yarn cache
uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: restore node_modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-
- name: restore typescript cache
uses: actions/cache@v3
with:
path: "**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
- name: yarn install
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: install
- name: yarn lerna bootstrap
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: lerna bootstrap
- name: build
uses: borales/actions-yarn@v4
with:
dir: webapp/packages/product-default
cmd: build # build tyescript source code
- name: test
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: lerna run test # build packages that have test script
+29
View File
@@ -0,0 +1,29 @@
name: cleanup caches by a branch
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
-45
View File
@@ -1,45 +0,0 @@
name: Frontend Lint
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
jobs:
lint:
name: Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: yarn
cache-dependency-path: webapp/yarn.lock
- name: restore node_modules
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.os }}-node_modules-${{ hashFiles('webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-
- name: yarn install --frozen-lockfile
uses: borales/actions-yarn@v5
with:
dir: webapp
cmd: install
- name: Lint
uses: reviewdog/action-eslint@v1
with:
reporter: github-pr-review
filter_mode: file
workdir: webapp
fail_on_error: true
eslint_flags: "--ext .ts,.tsx"
+80
View File
@@ -0,0 +1,80 @@
name: frontend
on:
# Triggers the workflow on push or pull request events but only for the "devel" branch
pull_request:
branches: ["devel"]
paths:
- "webapp/**" # monitor for the frontend changes
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allows you to reuse workflows by referencing their YAML files
workflow_call:
jobs:
build_and_test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./webapp
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "20"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: restore yarn cache
uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: restore node_modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-
- name: restore typescript cache
uses: actions/cache@v3
with:
path: "**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
- name: yarn install
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: install
- name: yarn lerna bootstrap
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: lerna bootstrap
- name: build
uses: borales/actions-yarn@v4
with:
dir: webapp/packages/product-default
cmd: bundle
- name: test
uses: borales/actions-yarn@v4
with:
dir: webapp
cmd: lerna run test
+17
View File
@@ -0,0 +1,17 @@
name: validation
on:
pull_request:
branches:
- devel
types:
- opened
- synchronize
- reopened
- edited
- ready_for_review
- labeled
jobs:
commit-message:
uses: dbeaver/dbeaver/.github/workflows/reused-commit-msgs-validator.yml@devel
+1 -1
View File
@@ -26,7 +26,7 @@ dist/
*.iml
/idea/
.idea/
## Mac OS
.DS_Store
+1
View File
@@ -15,6 +15,7 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
"VisualStudioExptTeam.intellicode-api-usage-examples",
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
+69 -53
View File
@@ -4,7 +4,7 @@
{
"type": "chrome",
"request": "launch",
"name": "CloudBeaver CE",
"name": "Chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/..",
"outFiles": [
@@ -17,60 +17,76 @@
},
{
"type": "java",
"name": "CloudBeaver CE Server",
"cwd": "${workspaceFolder}/../opt/cbce",
"name": "CloudBeaver CE",
"cwd": "${workspaceFolder}/workspace-dev-ce",
"preLaunchTask": "Generate dev proprties for CBCE",
"request": "launch",
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
"args": [
"-product",
"io.cloudbeaver.product.ce.product",
"-configuration",
"file:${workspaceFolder}/../dbeaver-workspace/products/CloudbeaverServer.product/",
"-dev",
"file:${workspaceFolder}/../dbeaver-workspace/products/CloudbeaverServer.product/dev.properties",
"-nl",
"en",
"-web-config",
"conf/cloudbeaver.conf",
"-registryMultiLanguage"
],
// "windows": {
// "args": ["-os", "win32", "-ws", "win32", "-arch", "x86_64"]
// },
// "osx": {
// "args": ["-os", "macosx", "-ws", "cocoa", "-arch", "aarch64"]
// },
"vmArgs": [
"-XX:+IgnoreUnrecognizedVMOptions",
"-Xms64m",
"-Xmx1024m",
"-Declipse.pde.launch=true",
"-XstartOnFirstThread",
"-Dfile.encoding=UTF-8",
"--add-modules=ALL-SYSTEM",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
"--add-opens=java.base/java.net=ALL-UNNAMED",
"--add-opens=java.base/java.nio=ALL-UNNAMED",
"--add-opens=java.base/java.nio.charset=ALL-UNNAMED",
"--add-opens=java.base/java.text=ALL-UNNAMED",
"--add-opens=java.base/java.time=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
"--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED",
"--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED",
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
"--add-opens=java.base/sun.security.ssl=ALL-UNNAMED",
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
"--add-opens=java.base/sun.security.util=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
"--add-opens=java.sql/java.sql=ALL-UNNAMED"
]
"windows": {
"type": "java",
"name": "CloudBeaver CE",
"request": "launch",
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
"args": [
"-product",
"io.cloudbeaver.product.ce.product",
"-configuration",
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/",
"-dev",
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/dev.properties",
"-os",
"win32",
"-ws",
"win32",
"-arch",
"x86_64",
"-nl",
"en",
"-showsplash",
"-web-config",
"conf/cloudbeaver.conf"
],
"vmArgs": [
"-XX:+IgnoreUnrecognizedVMOptions",
"--add-modules=ALL-SYSTEM",
"-Xms64m",
"-Xmx1024m",
"-Declipse.pde.launch=true"
]
},
"osx": {
"type": "java",
"name": "CloudBeaver CE",
"request": "launch",
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
"args": [
"-product",
"io.cloudbeaver.product.ce.product",
"-configuration",
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/",
"-dev",
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/dev.properties",
"-os",
"macosx",
"-ws",
"cocoa",
"-arch",
"aarch64",
"-nl",
"en",
"-showsplash",
"-web-config",
"conf/cloudbeaver.conf"
],
"vmArgs": [
"-XX:+IgnoreUnrecognizedVMOptions",
"--add-modules=ALL-SYSTEM",
"-Xms64m",
"-Xmx1024m",
"-Declipse.pde.launch=true",
"-XstartOnFirstThread"
]
}
}
]
}
+3 -1
View File
@@ -9,7 +9,9 @@
" *",
" * Licensed under the Apache License, Version 2.0.",
" * you may not use this file except in compliance with the License.",
" */"
" */",
"",
""
]
}
}
+18 -15
View File
@@ -17,7 +17,7 @@
"type": "shell",
"command": "yarn",
"group": "test",
"args": ["test"]
"args": ["lerna", "run", "test", "--no-bail", "--stream", "--"]
},
{
"label": "Run DevServer CE",
@@ -34,16 +34,27 @@
}
},
{
"label": "Generate Eclipse PDE CloudBeaver",
"label": "Generate dev proprties for CBCE",
"type": "shell",
"command": "mvn",
"osx": {
"command": "./generate_workspace.sh"
"args": [
"package",
"-q",
"exec:java",
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/Eclipse.app/Contents/Eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
]
},
"windows": {
"command": "./generate_workspace.cmd"
"args": [
"package",
"-q",
"exec:java",
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
]
},
"options": {
"cwd": "${workspaceFolder}/../cloudbeaver"
"cwd": "${workspaceFolder}/../idea-rcp-launch-config-generator"
},
"presentation": {
"reveal": "silent",
@@ -84,7 +95,7 @@
{
"label": "Yarn Install",
"type": "shell",
"command": "yarn install",
"command": "yarn install; yarn lerna bootstrap",
"args": [],
"presentation": {
"reveal": "silent",
@@ -95,15 +106,7 @@
{
"label": "Update TS references CE",
"type": "shell",
"command": "yarn run update-ts-references",
"options": {
"cwd": "${workspaceFolder}/webapp"
}
},
{
"label": "Clean CE",
"type": "shell",
"command": "yarn clean",
"command": "yarn lerna run update-ts-references --stream",
"options": {
"cwd": "${workspaceFolder}/webapp"
}
+43 -48
View File
@@ -1,16 +1,13 @@
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
# CloudBeaver Community
Cloud Database Manager - Community Edition.
CloudBeaver is a web server that provides a rich web interface. The server itself is a Java application, and the web part is written in TypeScript and React.
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
See our [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" width="250"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/connection-creation-demo.png" width="400"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/gis-demo.png" width="400"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/data-transfer-demo.png" width="400"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/sql-editor-demo.png" width="400"/>
Cloud Database Manager - Community Edition.
CloudBeaver is a web server which provides rich web interface. Server itself is a Java application, web part is written on TypeScript and React.
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
See out [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
![](https://github.com/dbeaver/cloudbeaver/wiki/images/demo_screenshot_1.png)
## Run in Docker
@@ -19,49 +16,47 @@ See our [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
## Demo server
You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
[Database access instructions](https://github.com/dbeaver/cloudbeaver/wiki/Demo-Server)
## Changelog
### 24.2.1. 2024-09-23
- Chinese localization has been improved (thanks to [cashlifei](https://github.com/cashlifei));
- Environment variables configuration has been improved - now you can configure more variables on the initial stage of the Docker setup;
- SQL Server driver has been updated to version 12.8.0.
### 24.0.1. 2024-03-25
- Added support for two modes for committing changes to the database:
- Auto-commit transfers all changes that you make immediately to the database;
- Manual commit requires your confirmation before committing a change to the database or rolling it back.
- Different bug fixes and enhancements have been made.
### 24.0.0. 2024-03-04
- Changes since 23.3.0:
### 24.2.0. 2024-09-02
### Changes since 24.1.0:
- General:
- French language support was added (thanks to [matthieukhl](https://github.com/matthieukhl))
- Added the ability to close editor tabs with the middle mouse button
- Added right-click support to open the context menu in the Metadata Editor
- The list of forbidden characters for naming and renaming resource manager files has been updated, and now it includes the following characters: / : " \ ' <> | ? *
- Application cookies security was improved
- Authentication:
- Improved LDAP authentication: added the ability to filter users via service account parameters and ability to specify custom unique user identifiers
- Data Editor:
- Added additional notifications about the restricted operations
- Enhanced IPv6 and DateTime32 data representation for Clickhouse
- Data editing was fixed for DuckDB
- SQL Editor:
- SQL Editor auto-completion was enhanced to get column, table, and function names faster
- Fixed the dollar-quoted string parsing in the SQL Editor for PostgreSQL
- Improved display of the long error messages in the SQL Editor and Log viewer
- Changed the save script icon to a floppy disk for better recognition
- Improved application behavior when closing a connection - open editors won't be closed on disconnect
- Main updates:
- Have enhanced security for unauthorised access;
- Have added a password policy for the local authorization. Password parameters can be set in the configuration file;
- Have added validation for mandatory fields in all forms to create and edit entities;
- Have added an ability to edit the default preferences of the following parts: interface, tools and data viewer in the settings panel in the administrative part;
- Performance:
- Have improved the performance of the server.
- Administration:
- Redesigned administration navigation panel - now it is more compact and clear
- Added the ability to change the default commit mode for each connection separately
- Added the ability to configure the server property rootURI parameter (thanks to [arioko](https://github.com/arioko))
- Have added the ability to configure reverse proxy header name and redirect URL at logout. Now the administrator can configure all settings. The delimiter symbol for the reverse proxy team headers is configurable;
- Preferences:
- Have added the ability in the admin panel to customise copying in the Tables and Values panel for all users except the administrator;
- Have added the ability to disable alias autocomplete for all users.
- Access Management:
- A default user group was added to the product. This group includes all users which could not be deleted.
- Connections:
- The 'Keep alive' option has been added to the connection settings to keep the connection active even in case of inactivity.
- Data Editor:
- Have added the ability to view decoded binary-type data in the Value panel;
- Text wrap is activated by default for texts and BLOBs in the Values panel for better visibility. The user can switch to the one-line mode using a button on the toolbar;
- Have added the ability to display full text for a string data type in the Value panel;
- Have added the ability to display the OpenStreetMap layer with the coordinate system ESPG 4326.
- Resource management:
- Read-only scripts now have a padlock icon.
- Databases:
- Added the "Keep alive" setting for Db2 LUW and IMB i, Apache Kyuubi, Clickhouse, Firebird, and Trino
- Updated Firebird driver to version 5.0.4
- DDL generation for Oracle Tablespaces was added (thanks to [pandya09](https://github.com/pandya09))
## 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 24.2.0 was made by:
1. [matthieukhl](https://github.com/matthieukhl)
- The DuckDB driver has been added;
- Updated PostgreSQL driver to 42.5.2;
- ClickHouse Legacy driver has been removed to prevent potential vulnerability issues;
- Updated MariaDB driver to version 3.3.2.
- Different bug fixes and enhancements have been made.
+1 -1
View File
@@ -17,7 +17,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<version>2.2.220</version>
</dependency>
</dependencies>
<build>
-104
View File
@@ -1,104 +0,0 @@
{
server: {
serverPort: "${CLOUDBEAVER_SERVICE_PORT:8978}",
workspaceLocation: "${CLOUDBEAVER_WORKSPACE_LOCATION:workspace}",
contentRoot: "web",
driversLocation: "drivers",
sslConfigurationPath:"${CLOUDBEAVER_SSL_CONF_PATH:workspace/.data/ssl-config.xml}",
rootURI: "${CLOUDBEAVER_ROOT_URI:/}",
serviceURI: "/api/",
productSettings: {
# Global properties
core.theming.theme: 'light',
core.localization.localization: 'en',
plugin.sql-editor.autoSave: true,
plugin.sql-editor.disabled: false,
# max size of the file that can be uploaded to the editor (in kilobytes)
plugin.sql-editor.maxFileSize: 10240,
plugin.log-viewer.disabled: false,
plugin.log-viewer.logBatchSize: 1000,
plugin.log-viewer.maxLogRecords: 2000,
sql.proposals.insert.table.alias: PLAIN
},
expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
enableSecurityManager: false,
sm: {
enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:true}",
maxFailedLogin: "${CLOUDBEAVER_MAX_FAILED_LOGINS:10}",
minimumLoginTimeout: "${CLOUDBEAVER_MINIMUM_LOGIN_TIMEOUT:1}",
blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}",
passwordPolicy: {
minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}",
requireMixedCase: "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}",
minNumberCount: "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}",
minSymbolCount: "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}"
}
},
database: {
driver: "${CLOUDBEAVER_DB_DRIVER:h2_embedded_v2}",
url: "${CLOUDBEAVER_DB_URL:jdbc:h2:${workspace}/.data/cb.h2v2.dat}",
schema: "${CLOUDBEAVER_DB_SCHEMA:''}",
user: "${CLOUDBEAVER_DB_USER:''}",
password: "${CLOUDBEAVER_DB_PASSWORD:''}",
initialDataConfiguration: "${CLOUDBEAVER_DB_INITIAL_DATA:conf/initial-data.conf}",
pool: {
minIdleConnections: "${CLOUDBEAVER_DB_MIN_IDLE_CONNECTIONS:4}",
maxIdleConnections: "${CLOUDBEAVER_DB_MAX_IDLE_CONNECTIONS:10}",
maxConnections: "${CLOUDBEAVER_DB_MAX_CONNECTIONS:100}",
validationQuery: "${CLOUDBEAVER_DB_VALIDATION_QUERY:SELECT 1}"
},
backupEnabled: "${CLOUDBEAVER_DB_BACKUP_ENABLED:true}"
}
},
app: {
anonymousAccessEnabled: "${CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED:true}",
anonymousUserRole: user,
defaultUserTeam: "${CLOUDBEAVER_APP_DEFAULT_USER_TEAM:user}",
grantConnectionsAccessToAnonymousTeam: "${CLOUDBEAVER_APP_GRANT_CONNECTIONS_ACCESS_TO_ANONYMOUS_TEAM:false}",
supportsCustomConnections: "${CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS:false}",
showReadOnlyConnectionInfo: "${CLOUDBEAVER_APP_READ_ONLY_CONNECTION_INFO:false}",
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
forwardProxy: "${CLOUDBEAVER_APP_FORWARD_PROXY:false}",
publicCredentialsSaveEnabled: "${CLOUDBEAVER_APP_PUBLIC_CREDENTIALS_SAVE_ENABLED:true}",
adminCredentialsSaveEnabled: "${CLOUDBEAVER_APP_ADMIN_CREDENTIALS_SAVE_ENABLED:true}",
resourceManagerEnabled: "${CLOUDBEAVER_APP_RESOURCE_MANAGER_ENABLED:true}",
resourceQuotas: {
dataExportFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_DATA_EXPORT_FILE_SIZE_LIMIT:10000000}",
resourceManagerFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_RESOURCE_MANAGER_FILE_SIZE_LIMIT:500000}",
sqlMaxRunningQueries: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_MAX_RUNNING_QUERIES:100}",
sqlResultSetRowsLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_RESULT_SET_ROWS_LIMIT:100000}",
sqlTextPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_TEXT_PREVIEW_MAX_LENGTH:4096}",
sqlBinaryPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_BINARY_PREVIEW_MAX_LENGTH:261120}"
},
enabledAuthProviders: [
"local"
],
disabledDrivers: [
"sqlite:sqlite_jdbc",
"h2:h2_embedded",
"h2:h2_embedded_v2",
"clickhouse:yandex_clickhouse"
],
disabledBetaFeatures: [
]
}
}
@@ -0,0 +1,100 @@
{
server: {
serverPort: 8978,
workspaceLocation: "workspace",
contentRoot: "web",
driversLocation: "drivers",
sslConfigurationPath:"${CLOUDBEAVER_SSL_CONF_PATH:workspace/.data/ssl-config.xml}",
rootURI: "/",
serviceURI: "/api/",
productSettings: {
# Global properties
core.theming.theme: 'light',
core.localization.localization: 'en',
plugin.sql-editor.autoSave: true,
plugin.sql-editor.disabled: false,
# max size of the file that can be uploaded to the editor (in kilobytes)
plugin.sql-editor.maxFileSize: 10240,
plugin.log-viewer.disabled: false,
plugin.log-viewer.logBatchSize: 1000,
plugin.log-viewer.maxFailedRequests: 3,
plugin.log-viewer.maxLogRecords: 2000,
plugin.log-viewer.refreshTimeout: 3000,
sql.proposals.insert.table.alias: PLAIN
},
expireSessionAfterPeriod: 1800000,
develMode: false,
enableSecurityManager: false,
sm: {
enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:true}",
maxFailedLogin: "${CLOUDBEAVER_MAX_FAILED_LOGINS:10}",
minimumLoginTimeout: "${CLOUDBEAVER_MINIMUM_LOGIN_TIMEOUT:1}",
blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}",
passwordPolicy: {
minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}",
requireMixedCase: "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}",
minNumberCount: "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}",
minSymbolCount: "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}"
}
},
database: {
driver: "h2_embedded_v2",
url: "jdbc:h2:${workspace}/.data/cb.h2v2.dat",
initialDataConfiguration: "conf/initial-data.conf",
pool: {
minIdleConnections: 4,
maxIdleConnections: 10,
maxConnections: 100,
validationQuery: "SELECT 1"
}
}
},
app: {
anonymousAccessEnabled: true,
anonymousUserRole: "user",
defaultUserTeam: "user",
grantConnectionsAccessToAnonymousTeam: false,
supportsCustomConnections: false,
showReadOnlyConnectionInfo: false,
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
forwardProxy: false,
publicCredentialsSaveEnabled: true,
adminCredentialsSaveEnabled: true,
resourceManagerEnabled: true,
resourceQuotas: {
dataExportFileSizeLimit: 10000000,
resourceManagerFileSizeLimit: 500000,
sqlMaxRunningQueries: 100,
sqlResultSetRowsLimit: 100000,
sqlResultSetMemoryLimit: 2000000,
sqlTextPreviewMaxLength: 4096,
sqlBinaryPreviewMaxLength: 261120
},
enabledAuthProviders: [
"local"
],
disabledDrivers: [
"sqlite:sqlite_jdbc",
"h2:h2_embedded",
"h2:h2_embedded_v2",
"clickhouse:yandex_clickhouse"
]
}
}
+3
View File
@@ -0,0 +1,3 @@
### Sample databases
Provides access to locally deployed SQLite sample database
@@ -0,0 +1,39 @@
{
"folders": {},
"connections": {
"sqlite_xerial-sample-database": {
"provider": "generic",
"driver": "sqlite_jdbc",
"name": "SQLite - Chinook (Sample)",
"save-password": true,
"navigator-show-only-entities": false,
"navigator-hide-folders": false,
"read-only": false,
"template": false,
"configuration": {
"database": "${application.path}/../samples/db/Chinook.sqlitedb",
"type": "dev",
"auth-model": "native"
}
},
"postgresql-template-1": {
"provider": "postgresql",
"driver": "postgres-jdbc",
"name": "PostgreSQL (Template)",
"save-password": false,
"show-system-objects": false,
"read-only": true,
"template": true,
"configuration": {
"host": "localhost",
"port": "5432",
"database": "postgres",
"url": "jdbc:postgresql://localhost:5432/postgres",
"type": "dev",
"provider-properties": {
"@dbeaver-show-non-default-db@": "false"
}
}
}
}
}
@@ -0,0 +1,4 @@
{
"folders": {},
"connections": {}
}
@@ -0,0 +1,95 @@
{
server: {
serverPort: 8978,
workspaceLocation: "workspace",
contentRoot: "web",
driversLocation: "drivers",
rootURI: "/",
serviceURI: "/api/",
productSettings: {
# Global properties
core.theming.theme: 'light',
core.localization.localization: 'en',
plugin.sql-editor.autoSave: true,
plugin.sql-editor.disabled: false,
# max size of the file that can be uploaded to the editor (in kilobytes)
plugin.sql-editor.maxFileSize: 10240,
plugin.log-viewer.disabled: false,
plugin.log-viewer.logBatchSize: 1000,
plugin.log-viewer.maxFailedRequests: 3,
plugin.log-viewer.maxLogRecords: 2000,
plugin.log-viewer.refreshTimeout: 3000,
sql.proposals.insert.table.alias: PLAIN
},
expireSessionAfterPeriod: 1800000,
develMode: false,
enableSecurityManager: false,
database: {
driver:"h2_embedded_v2",
url: "jdbc:h2:${workspace}/.data/cb.h2v2.dat",
initialDataConfiguration: "conf/initial-data.conf",
pool: {
minIdleConnections: 4,
maxIdleConnections: 10,
maxConnections: 100,
validationQuery: "SELECT 1"
}
},
sm: {
enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:true}",
maxFailedLogin: "${CLOUDBEAVER_MAX_FAILED_LOGINS:10}",
minimumLoginTimeout: "${CLOUDBEAVER_MINIMUM_LOGIN_TIMEOUT:1}",
blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}",
passwordPolicy: {
minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}",
requireMixedCase: "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}",
minNumberCount: "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}",
minSymbolCount: "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}"
}
}
},
app: {
anonymousAccessEnabled: true,
anonymousUserRole: "user",
grantConnectionsAccessToAnonymousTeam: false,
supportsCustomConnections: false,
showReadOnlyConnectionInfo: false,
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
forwardProxy: false,
publicCredentialsSaveEnabled: true,
adminCredentialsSaveEnabled: true,
resourceManagerEnabled: true,
resourceQuotas: {
dataExportFileSizeLimit: 10000000,
resourceManagerFileSizeLimit: 500000,
sqlMaxRunningQueries: 100,
sqlResultSetRowsLimit: 100000,
sqlResultSetMemoryLimit: 2000000,
sqlTextPreviewMaxLength: 4096,
sqlBinaryPreviewMaxLength: 261120
},
enabledAuthProviders: [
"local"
],
disabledDrivers: [
"h2:h2_embedded",
"h2:h2_embedded_v2",
"clickhouse:yandex_clickhouse"
]
}
}
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
Chinook Database - Version 1.3
Script: Chinook_Sqlite.sql
Description: Creates and populates the Chinook database.
DB Server: Sqlite
Author: Luis Rocha
License: http://www.codeplex.com/ChinookDatabase/license
+20 -1
View File
@@ -2,6 +2,12 @@
set -Eeo pipefail
set +u
# #command line arguments
# CONFIGURATION_PATH=${1-"../config/sample-databases/DefaultConfiguration"}
# SAMPLE_DATABASE_PATH=${2-""}
# echo $CONFIGURATION_PATH
# echo $SAMPLE_DATABASE_PATH
echo "Clone and build Cloudbeaver"
rm -rf ./drivers
@@ -10,6 +16,7 @@ mkdir ./cloudbeaver
mkdir ./cloudbeaver/server
mkdir ./cloudbeaver/conf
mkdir ./cloudbeaver/workspace
mkdir ./cloudbeaver/web
echo "Pull cloudbeaver platform"
@@ -37,8 +44,20 @@ cp -rp ../server/product/web-server/target/products/io.cloudbeaver.product/all/a
cp -p ./scripts/* ./cloudbeaver
mkdir cloudbeaver/samples
if [[ -z $SAMPLE_DATABASE_PATH ]]; then
SAMPLE_DATABASE_PATH=""
else
mkdir cloudbeaver/samples/db
cp -rp "${SAMPLE_DATABASE_PATH}" cloudbeaver/samples/
fi
if [[ -z "$CONFIGURATION_PATH" ]]; then
CONFIGURATION_PATH="../config/sample-databases/DefaultConfiguration"
fi
cp -rp ../config/core/* cloudbeaver/conf
cp -rp ../config/GlobalConfiguration/.dbeaver/data-sources.json cloudbeaver/conf/initial-data-sources.conf
cp -rp "${CONFIGURATION_PATH}"/GlobalConfiguration/.dbeaver/data-sources.json cloudbeaver/conf/initial-data-sources.conf
cp -p "${CONFIGURATION_PATH}"/*.conf cloudbeaver/conf/
mv drivers cloudbeaver
echo "End of backend build"
+2 -12
View File
@@ -3,25 +3,15 @@ set -Eeuo pipefail
echo "Build static content"
mkdir ./cloudbeaver/web
cd ../../cloudbeaver/webapp
yarn
cd ./packages/product-default
yarn run bundle
yarn lerna run bootstrap
yarn lerna run bundle --no-bail --stream --scope=@cloudbeaver/product-default #-- -- --env source-map
if [[ "$?" -ne 0 ]] ; then
echo 'Application build failed'; exit $rc
fi
cd ../../
yarn test
if [[ "$?" -ne 0 ]] ; then
echo 'Frontend tests failed'; exit $rc
fi
cd ../deploy
echo "Copy static content"
+1
View File
@@ -0,0 +1 @@
@call build.bat ..\config\sample-databases\SQLiteConfiguration ..\config\sample-databases\db
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -Eeuo pipefail
#command line arguments
CONFIGURATION_PATH='../config/sample-databases/SQLiteConfiguration'
SAMPLE_DATABASE_PATH='../config/sample-databases/db'
source build-backend.sh
source build-frontend.sh
+11 -2
View File
@@ -1,6 +1,11 @@
@echo off
rem command line arguments
SET CONFIGURATION_PATH=%1
SET SAMPLE_DATABASE_PATH=%2
IF "%CONFIGURATION_PATH%"=="" SET CONFIGURATION_PATH="..\config\sample-databases\DefaultConfiguration"
echo "Configuration path=%CONFIGURATION_PATH%"
echo Clone and build Cloudbeaver
@@ -35,9 +40,13 @@ xcopy /E /Q ..\server\product\web-server\target\products\io.cloudbeaver.product\
copy scripts\* cloudbeaver >NUL
mkdir cloudbeaver\samples
IF NOT "%SAMPLE_DATABASE_PATH%"=="" (
mkdir cloudbeaver\samples\db
xcopy /E /Q %SAMPLE_DATABASE_PATH% cloudbeaver\samples\db >NUL
)
copy ..\config\core\* cloudbeaver\conf >NUL
copy ..\config\DefaultConfiguration\GlobalConfiguration\.dbeaver\data-sources.json cloudbeaver\conf\initial-data-sources.conf >NUL
copy %CONFIGURATION_PATH%\GlobalConfiguration\.dbeaver\data-sources.json cloudbeaver\conf\initial-data-sources.conf >NUL
copy %CONFIGURATION_PATH%\*.conf cloudbeaver\conf >NUL
move drivers cloudbeaver >NUL
+4
View File
@@ -1,5 +1,9 @@
#!/bin/bash
set -Eeuo pipefail
#command line arguments
CONFIGURATION_PATH="../config/sample-databases/DefaultConfiguration"
SAMPLE_DATABASE_PATH=""
source build-backend.sh
source build-frontend.sh
@@ -1,12 +1,10 @@
FROM ubuntu:24.04
FROM ubuntu:23.10
MAINTAINER DBeaver Corp, devops@dbeaver.com
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# curl required for historical reasons, see https://github.com/adoptium/containers/issues/255
curl \
wget \
@@ -45,3 +43,10 @@ RUN set -eux; \
### Patch java security
COPY java.security* ${JAVA_HOME}/conf/security/java.security
COPY cloudbeaver /opt/cloudbeaver
EXPOSE 8978
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
WORKDIR /opt/cloudbeaver/
ENTRYPOINT ["./run-server.sh"]
-13
View File
@@ -1,13 +0,0 @@
FROM dbeaver/base-java
MAINTAINER DBeaver Corp, devops@dbeaver.com
RUN apt-get update; \
apt-get upgrade -y;
COPY cloudbeaver /opt/cloudbeaver
EXPOSE 8978
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
WORKDIR /opt/cloudbeaver/
ENTRYPOINT ["./run-server.sh"]
+1 -1
View File
@@ -1,3 +1,3 @@
cd ..
docker build -t dbeaver/cloudbeaver:dev . --file ./docker/cloudbeaver-ce/Dockerfile
docker build -t dbeaver/cloudbeaver:dev . --file ./docker/Dockerfile
+1 -1
View File
@@ -9,7 +9,7 @@ echo "Starting Cloudbeaver Server"
&& [ ! -f "workspace/GlobalConfiguration/.dbeaver/data-sources.json" ] \
&& cp conf/initial-data-sources.conf workspace/GlobalConfiguration/.dbeaver/data-sources.json
exec java ${JAVA_OPTS} \
java ${JAVA_OPTS} \
-Dfile.encoding=UTF-8 \
--add-modules=ALL-SYSTEM \
--add-opens=java.base/java.io=ALL-UNNAMED \
+10
View File
@@ -0,0 +1,10 @@
[program:cloudbeaver]
directory=/opt/cloudbeaver/
command=/opt/cloudbeaver/run-server.sh
stopasgroup=true
killasgroup=true
stopsignal=INT
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/cloudbeaver-server.log
-3
View File
@@ -1,3 +0,0 @@
SET mypath=%~dp0
.\..\idea-rcp-launch-config-generator\runGenerator.cmd -f %mypath%
-1
View File
@@ -1 +0,0 @@
./../idea-rcp-launch-config-generator/runGenerator.sh -f "$PWD"
+1
View File
@@ -1,3 +1,4 @@
{
"projects": ["../dbeaver-common", "../dbeaver", "../cloudbeaver/server"],
"targetPlatform": "./org.jkiss.cloudbeaver.tp.target"
}
+3 -2
View File
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde version="3.8"?>
<target name="org.jkiss.cloudbeaver.tp">
<target name="org.jkiss.tp.cloudbeaver">
<locations>
<location path="../dbeaver-workspace/dependencies/plugins" type="Directory"/>
<location path="../eclipse/Eclipse.app/Contents/Eclipse/plugins" type="Directory"/>
<location path="../eclipse/eclipse/plugins" type="Directory"/>
</locations>
</target>
-33
View File
@@ -1,33 +0,0 @@
workspaceName=cloudbeaver-ce
featuresPaths=\
dbeaver/features;\
cloudbeaver/server/features;
bundlesPaths=\
dbeaver-common/modules;\
dbeaver/plugins;\
cloudbeaver/server/bundles;
testLibraries=\
org.junit;\
org.mockito.mockito-core;\
junit-jupiter-api;\
org.opentest4j
repositories=\
https://p2.dev.dbeaver.com/eclipse-repo/;\
https://download.eclipse.org/releases/${eclipse-version}/;
testBundles=\
org.junit;\
org.mockito.mockito-core;\
junit-jupiter-api;\
org.opentest4j
productsPaths=\
dbeaver/product/community/DBeaver.product;\
cloudbeaver/server/product/web-server/CloudbeaverServer.product:../../opt/cloudbeaver;
ideaConfigurationFilesPaths=\
dbeaver/.ide/.idea;
testBundlePaths=\
dbeaver/test;\
cloudbeaver/server/test;
additionalModuleRoots=\
opt;
optionalFeatureRepositories=\
dbeaver/product/repositories
@@ -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.62.qualifier
Bundle-Release-Date: 20241007
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -18,19 +18,16 @@ Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.bundle.graphql.java;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
com.google.gson;visibility:=reexport,
jakarta.servlet-api;bundle-version:="6.0.0";visibility:=reexport
jakarta.servlet;visibility:=reexport
Export-Package: io.cloudbeaver,
io.cloudbeaver.auth,
io.cloudbeaver.auth.provider,
io.cloudbeaver.auth.provider.fa,
io.cloudbeaver.auth.provider.local,
io.cloudbeaver.auth.provisioning,
io.cloudbeaver.websocket,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
io.cloudbeaver.model.config,
io.cloudbeaver.model.fs,
io.cloudbeaver.model.log,
io.cloudbeaver.model.rm,
io.cloudbeaver.model.rm.local,
io.cloudbeaver.model.rm.lock,
@@ -43,7 +40,6 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.service.admin,
io.cloudbeaver.service.security,
io.cloudbeaver.service.sql,
io.cloudbeaver.test,
io.cloudbeaver.utils,
io.cloudbeaver.utils.file
Automatic-Module-Name: io.cloudbeaver.model
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.62-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -16,21 +16,23 @@
*/
package io.cloudbeaver;
import org.eclipse.core.resources.IProject;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.registry.BaseProjectImpl;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
import java.nio.file.Path;
import java.util.Collection;
public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
public class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
@NotNull
private final RMProject project;
@@ -38,18 +40,21 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
@NotNull
private final Path path;
@NotNull
protected final DataSourceFilter dataSourceFilter;
private final RMController resourceController;
public BaseWebProjectImpl(
@NotNull DBPWorkspace workspace,
@NotNull RMController resourceController,
@NotNull SMSessionContext sessionContext,
@NotNull RMProject project
@NotNull RMProject project,
@NotNull DataSourceFilter dataSourceFilter
) {
super(workspace, sessionContext);
this.resourceController = resourceController;
this.path = RMUtils.getProjectPath(project);
this.project = project;
this.dataSourceFilter = dataSourceFilter;
}
@NotNull
@@ -86,6 +91,12 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return path;
}
@Nullable
@Override
public IProject getEclipseProject() {
return null;
}
@Override
public boolean isOpen() {
return true;
@@ -101,6 +112,11 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return false;
}
@NotNull
public RMProject getRmProject() {
return this.project;
}
/**
* Method for Bulk Update of resources properties paths
*
@@ -35,7 +35,6 @@ public interface DBWConstants {
String PERMISSION_EDIT_DATA = "edit-data";
String STATE_ATTR_SIGN_IN_STATE = "state.signin";
String WORK_DATA_FOLDER_NAME = ".work-data";
enum SignInState {
GLOBAL,
@@ -21,7 +21,7 @@ import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.sql.SQLState;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
@@ -38,7 +38,6 @@ public class DBWebException extends DBException implements GraphQLError {
public static final String ERROR_CODE_SESSION_EXPIRED = "sessionExpired";
public static final String ERROR_CODE_ACCESS_DENIED = "accessDenied";
public static final String ERROR_CODE_SERVER_NOT_INITIALIZED = "serverNotInitialized";
public static final String ERROR_CODE_LICENSE_DENIED = "licenseRequired";
public static final String ERROR_CODE_IDENT_REQUIRED = "identRequired";
public static final String ERROR_CODE_AUTH_REQUIRED = "authRequired";
@@ -66,8 +65,12 @@ public class DBWebException extends DBException implements GraphQLError {
this.webErrorCode = errorCode;
}
public DBWebException(Throwable cause) {
super(null, cause);
public DBWebException(Throwable cause, DBPDataSource dataSource) {
super(cause, dataSource);
}
public DBWebException(String message, Throwable cause, DBPDataSource dataSource) {
super(makeMessage(message, cause), cause, dataSource);
}
public String getWebErrorCode() {
@@ -125,14 +128,14 @@ public class DBWebException extends DBException implements GraphQLError {
//stString = stString.substring(divPos + 1).trim();
}
extensions.put("stackTrace", stString.trim());
int errorCode = SQLState.getCodeFromException(this);
int errorCode = getErrorCode();
if (errorCode != ERROR_CODE_NONE) {
extensions.put("errorCode", errorCode);
}
if (!CommonUtils.isEmpty(webErrorCode)) {
extensions.put("webErrorCode", webErrorCode);
}
String databaseState = SQLState.getStateFromException(this);
String databaseState = getDatabaseState();
if (databaseState != null) {
extensions.put("databaseState", databaseState);
}
@@ -29,7 +29,6 @@ import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.net.DBWNetworkProfile;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
@@ -261,7 +260,7 @@ public class WebDataSourceRegistryProxy implements DBPDataSourceRegistry, DataSo
}
@Override
public void removeAuthProfile(@NotNull DBAAuthProfile profile) {
public void removeAuthProfile(DBAAuthProfile profile) {
dataSourceRegistry.removeAuthProfile(profile);
}
@@ -323,12 +322,6 @@ public class WebDataSourceRegistryProxy implements DBPDataSourceRegistry, DataSo
return dataSourceRegistry.getTemporaryFolders();
}
@NotNull
@Override
public DBPPreferenceStore getPreferenceStore() {
return dataSourceRegistry.getPreferenceStore();
}
@Override
public boolean loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@@ -1,36 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import io.cloudbeaver.model.session.WebHeadlessSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.rm.RMProject;
public class WebHeadlessSessionProjectImpl extends WebProjectImpl {
public WebHeadlessSessionProjectImpl(
@NotNull WebHeadlessSession session,
@NotNull RMProject project
) {
super(
session.getWorkspace(),
session.getUserContext().getRmController(),
session.getSessionContext(),
project,
session.getUserContext().getPreferenceStore()
);
}
}
@@ -23,26 +23,22 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.task.DBTTaskManager;
import org.jkiss.dbeaver.registry.rm.DataSourceRegistryRM;
import org.jkiss.dbeaver.runtime.DBWorkbench;
public abstract class WebProjectImpl extends BaseWebProjectImpl {
private static final Log log = Log.getLog(WebProjectImpl.class);
@NotNull
protected final DBPPreferenceStore preferenceStore;
public WebProjectImpl(
@NotNull DBPWorkspace workspace,
@NotNull RMController resourceController,
@NotNull SMSessionContext sessionContext,
@NotNull RMProject project,
@NotNull DBPPreferenceStore preferenceStore
@NotNull DataSourceFilter dataSourceFilter
) {
super(workspace, resourceController, sessionContext, project);
this.preferenceStore = preferenceStore;
super(workspace, resourceController, sessionContext, project, dataSourceFilter);
}
@Nullable
@@ -70,24 +66,13 @@ public abstract class WebProjectImpl extends BaseWebProjectImpl {
return DBWorkbench.isDistributed();
}
@NotNull
@Override
public DBTTaskManager getTaskManager() {
throw new IllegalStateException("Task manager not supported");
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new WebDataSourceRegistryProxy(
new DataSourceRegistryRM(this, getResourceController(), preferenceStore),
getDataSourceFilter()
new DataSourceRegistryRM(this, getResourceController()),
dataSourceFilter
);
}
@NotNull
public DataSourceFilter getDataSourceFilter() {
return (ds) -> true;
}
}
@@ -1,118 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPEvent;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.security.user.SMObjectPermissions;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Global project.
* Connections there can be not accessible.
*/
public class WebSessionGlobalProjectImpl extends WebSessionProjectImpl {
private static final Log log = Log.getLog(WebSessionGlobalProjectImpl.class);
private Set<String> accessibleConnectionIds = Collections.emptySet();
public WebSessionGlobalProjectImpl(@NotNull WebSession webSession, @NotNull RMProject project) {
super(webSession, project);
}
/**
* Update info about accessible connections from a database.
*/
public synchronized void refreshAccessibleConnectionIds() {
this.accessibleConnectionIds = readAccessibleConnectionIds();
}
@NotNull
private Set<String> readAccessibleConnectionIds() {
try {
return webSession.getSecurityController()
.getAllAvailableObjectsPermissions(SMObjectType.datasource)
.stream()
.map(SMObjectPermissions::getObjectId)
.collect(Collectors.toSet());
} catch (DBException e) {
webSession.addSessionError(e);
log.error("Error reading connection grants", e);
return Collections.emptySet();
}
}
/**
* Checks if connection is accessible for current user.
*/
public boolean isDataSourceAccessible(@NotNull DBPDataSourceContainer dataSource) {
return dataSource.isExternallyProvided() ||
dataSource.isTemporary() ||
webSession.hasPermission(DBWConstants.PERMISSION_ADMIN) ||
accessibleConnectionIds.contains(dataSource.getId());
}
/**
* Adds a connection if it became accessible.
* The method is processed when connection permissions were updated.
*/
public synchronized void addAccessibleConnectionToCache(@NotNull String dsId) {
if (!getRMProject().isGlobal()) {
return;
}
this.accessibleConnectionIds.add(dsId);
var registry = getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
addConnection(dataSource);
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_ADD, dataSource, true));
}
}
/**
* Removes a connection if it became not accessible.
* The method is processed when connection permissions were updated.
*/
public synchronized void removeAccessibleConnectionFromCache(@NotNull String dsId) {
if (!getRMProject().isGlobal()) {
return;
}
var registry = getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
this.accessibleConnectionIds.remove(dsId);
removeConnection(dataSource);
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_REMOVE, dataSource));
dataSource.dispose();
}
}
@NotNull
public DataSourceFilter getDataSourceFilter() {
return this::isDataSourceAccessible;
}
}
@@ -16,41 +16,18 @@
*/
package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.websocket.event.WSEventType;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import java.util.*;
import java.util.stream.Collectors;
public class WebSessionProjectImpl extends WebProjectImpl {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
protected final WebSession webSession;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private boolean registryIsLoaded = false;
public WebSessionProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project
) {
super(
webSession.getWorkspace(),
webSession.getRmController(),
webSession.getSessionContext(),
project,
webSession.getUserPreferenceStore()
);
private final WebSession webSession;
public WebSessionProjectImpl(@NotNull WebSession webSession, @NotNull RMProject project, @NotNull DataSourceFilter dataSourceFilter) {
super(webSession.getWorkspace(), webSession.getRmController(), webSession.getSessionContext(), project, dataSourceFilter);
this.webSession = webSession;
}
@@ -59,156 +36,4 @@ public class WebSessionProjectImpl extends WebProjectImpl {
public DBNModel getNavigatorModel() {
return webSession.getNavigatorModel();
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
DBPDataSourceRegistry dataSourceRegistry = super.createDataSourceRegistry();
dataSourceRegistry.setAuthCredentialsProvider(webSession);
return dataSourceRegistry;
}
private synchronized void addDataSourcesToCache() {
if (registryIsLoaded) {
return;
}
getDataSourceRegistry().getDataSources().forEach(this::addConnection);
Throwable lastError = getDataSourceRegistry().getLastError();
if (lastError != null) {
webSession.addSessionError(lastError);
log.error("Error refreshing connections from project '" + getId() + "'", lastError);
}
registryIsLoaded = true;
}
@Override
public void dispose() {
super.dispose();
Map<String, WebConnectionInfo> conCopy;
synchronized (this.connections) {
conCopy = new HashMap<>(this.connections);
this.connections.clear();
}
for (WebConnectionInfo connectionInfo : conCopy.values()) {
if (connectionInfo.isConnected()) {
new DisconnectJob(connectionInfo.getDataSourceContainer()).schedule();
}
}
}
/**
* Returns web connection info from cache (if exists).
*/
@Nullable
public WebConnectionInfo findWebConnectionInfo(@NotNull String connectionId) {
synchronized (connections) {
return connections.get(connectionId);
}
}
/**
* Returns web connection info from cache, adds it to cache if not present.
* Throws exception if connection is not found.
*/
@NotNull
public WebConnectionInfo getWebConnectionInfo(@NotNull String connectionId) throws DBWebException {
WebConnectionInfo connectionInfo = findWebConnectionInfo(connectionId);
if (connectionInfo != null) {
return connectionInfo;
}
DBPDataSourceContainer dataSource = getDataSourceRegistry().getDataSource(connectionId);
if (dataSource != null) {
return addConnection(dataSource);
}
throw new DBWebException("Connection '%s' not found".formatted(connectionId));
}
/**
* Adds connection to project cache.
*/
@NotNull
public synchronized WebConnectionInfo addConnection(@NotNull DBPDataSourceContainer dataSourceContainer) {
WebConnectionInfo connection = new WebConnectionInfo(webSession, dataSourceContainer);
synchronized (connections) {
connections.put(dataSourceContainer.getId(), connection);
}
return connection;
}
/**
* Removes connection from project cache.
*/
public void removeConnection(@NotNull DBPDataSourceContainer dataSourceContainer) {
WebConnectionInfo webConnectionInfo = connections.get(dataSourceContainer.getId());
if (webConnectionInfo != null) {
webConnectionInfo.clearCache();
synchronized (connections) {
connections.remove(dataSourceContainer.getId());
}
}
}
/**
* Loads connection from registry if they are not loaded.
*
* @return connections from cache.
*/
public List<WebConnectionInfo> getConnections() {
if (!registryIsLoaded) {
addDataSourcesToCache();
registryIsLoaded = true;
}
synchronized (connections) {
return new ArrayList<>(connections.values());
}
}
/**
* updates data sources based on event in web session
*
* @param dataSourceIds list of updated connections
* @param type type of event
*/
public synchronized boolean updateProjectDataSources(@NotNull List<String> dataSourceIds, @NotNull WSEventType type) {
var sendDataSourceUpdatedEvent = false;
DBPDataSourceRegistry registry = getDataSourceRegistry();
// save old connections
var oldDataSources = dataSourceIds.stream()
.map(registry::getDataSource)
.filter(Objects::nonNull)
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
ds -> new DataSourceDescriptor((DataSourceDescriptor) ds, ds.getRegistry())
));
if (type == WSEventType.DATASOURCE_CREATED || type == WSEventType.DATASOURCE_UPDATED) {
registry.refreshConfig(dataSourceIds);
}
for (String dsId : dataSourceIds) {
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
if (ds == null) {
continue;
}
switch (type) {
case DATASOURCE_CREATED -> {
addConnection(ds);
sendDataSourceUpdatedEvent = true;
}
case DATASOURCE_UPDATED -> // if settings were changed we need to send event
sendDataSourceUpdatedEvent |= !ds.equalSettings(oldDataSources.get(dsId));
case DATASOURCE_DELETED -> {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
dsrc.removeDataSourceFromList(ds);
}
removeConnection(ds);
sendDataSourceUpdatedEvent = true;
}
default -> {
}
}
}
return sendDataSourceUpdatedEvent;
}
}
@@ -25,7 +25,6 @@ public interface CBAuthConstants {
String CB_AUTH_ID_REQUEST_PARAM = "authId";
String CB_AUTO_LOGIN_REQUEST_PARAM = "autoLogin";
String CB_REDIRECT_URL_REQUEST_PARAM = "redirectUrl";
String CB_FORCE_SESSIONS_LOGOUT = "forceSessionsLogout";
// Default max idle time (10 minutes)
long MAX_SESSION_IDLE_TIME = 10 * 60 * 1000;
@@ -46,9 +46,6 @@ public interface SMAuthProviderFederated extends SMSignOutLinkProvider {
@Nullable
String getAcsLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
@Nullable
String getEntityIdLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
@Nullable
default String getRedirectLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException {
return null;
@@ -1,79 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.auth.provider.fa;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.auth.*;
import java.time.LocalDateTime;
import java.util.Map;
public abstract class AbstractSessionExternal implements SMSessionExternal {
@NotNull
protected final Map<String, Object> authParameters;
@NotNull
protected final SMSession parentSession;
@NotNull
protected final SMAuthSpace space;
protected AbstractSessionExternal(
@NotNull SMSession parentSession,
@NotNull SMAuthSpace space,
@NotNull Map<String, Object> authParameters
) {
this.parentSession = parentSession;
this.space = space;
this.authParameters = authParameters;
}
@NotNull
@Override
public SMAuthSpace getSessionSpace() {
return space;
}
@NotNull
@Override
public SMSessionContext getSessionContext() {
return this.parentSession.getSessionContext();
}
@Nullable
@Override
public SMSessionPrincipal getSessionPrincipal() {
return parentSession.getSessionPrincipal();
}
@NotNull
@Override
public LocalDateTime getSessionStart() {
return parentSession.getSessionStart();
}
@Override
public void close() {
// do nothing
}
@Override
public Map<String, Object> getAuthParameters() {
return authParameters;
}
}
@@ -24,11 +24,12 @@ import io.cloudbeaver.service.sql.WebDataFormat;
import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebCommonUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.admin.sessions.DBAServerSessionManager;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPDataSourceFolder;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPAuthModelDescriptor;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
@@ -39,7 +40,6 @@ import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.model.rm.RMConstants;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
import org.jkiss.dbeaver.model.runtime.DBRRunnableParametrized;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -56,17 +56,6 @@ public class WebConnectionInfo {
private static final Log log = Log.getLog(WebConnectionInfo.class);
public static final String SECURED_VALUE = "********";
private static final String FEATURE_HAS_TOOLS = "hasTools";
private static final String FEATURE_CONNECTED = "connected";
private static final String FEATURE_VIRTUAL = "virtual";
private static final String FEATURE_TEMPORARY = "temporary";
private static final String FEATURE_READ_ONLY = "readOnly";
private static final String FEATURE_PROVIDED = "provided";
private static final String FEATURE_MANAGEABLE = "manageable";
private static final String TOOL_SESSION_MANAGER = "sessionManager";
private final WebSession session;
private final DBPDataSourceContainer dataSourceContainer;
private WebServerError connectError;
@@ -254,25 +243,22 @@ public class WebConnectionInfo {
List<String> features = new ArrayList<>();
if (dataSourceContainer.isConnected()) {
features.add(FEATURE_CONNECTED);
if (!getTools().isEmpty()) {
features.add(FEATURE_HAS_TOOLS);
}
features.add("connected");
}
if (dataSourceContainer.isHidden()) {
features.add(FEATURE_VIRTUAL);
features.add("virtual");
}
if (dataSourceContainer.isTemporary()) {
features.add(FEATURE_TEMPORARY);
features.add("temporary");
}
if (dataSourceContainer.isConnectionReadOnly()) {
features.add(FEATURE_READ_ONLY);
features.add("readOnly");
}
if (dataSourceContainer.isProvided()) {
features.add(FEATURE_PROVIDED);
features.add("provided");
}
if (dataSourceContainer.isManageable()) {
features.add(FEATURE_MANAGEABLE);
features.add("manageable");
}
return features.toArray(new String[0]);
@@ -398,16 +384,6 @@ public class WebConnectionInfo {
}
}
@Property
public Map<String, String> getMainPropertyValues() {
Map<String, String> mainProperties = new LinkedHashMap<>();
mainProperties.put(DBConstants.PROP_HOST, getHost());
mainProperties.put(DBConstants.PROP_PORT, getPort());
mainProperties.put(DBConstants.PROP_DATABASE, getDatabaseName());
mainProperties.put(DBConstants.PROP_SERVER, getServerName());
return mainProperties;
}
@Property
public Map<String, String> getProviderProperties() {
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
@@ -445,10 +421,10 @@ public class WebConnectionInfo {
private boolean hasProjectPermission(RMProjectPermission projectPermission) {
DBPProject project = dataSourceContainer.getProject();
if (!(project instanceof WebProjectImpl webProject)) {
if (!(project instanceof WebProjectImpl)) {
return false;
}
return SMUtils.hasProjectPermission(session, webProject.getRMProject(), projectPermission);
return SMUtils.hasProjectPermission(session, ((WebProjectImpl) project).getRmProject(), projectPermission);
}
private boolean canViewReadOnlyConnections() {
@@ -472,15 +448,6 @@ public class WebConnectionInfo {
return dataSourceContainer.getConnectionConfiguration().getKeepAliveInterval();
}
@Property
public boolean isAutocommit() {
Boolean isAutoCommit = dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
if (isAutoCommit == null) {
return true;
}
return isAutoCommit;
}
@Property
public List<WebSecretInfo> getSharedSecrets() throws DBException {
return dataSourceContainer.listSharedCredentials()
@@ -489,18 +456,4 @@ public class WebConnectionInfo {
.collect(Collectors.toList());
}
@NotNull
@Property
public List<String> getTools() {
if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) {
return List.of();
}
List<String> tools = new ArrayList<>();
// checks inside that datasource is not null in container, and it is adaptable to session manager class
if (DBUtils.getAdapter(DBAServerSessionManager.class, dataSourceContainer) != null) {
tools.add(TOOL_SESSION_MANAGER);
}
return tools;
}
}
@@ -53,13 +53,11 @@ public class WebProjectInfo {
}
@Property
public boolean isGlobal() {
return project.getRMProject().isGlobal();
}
public boolean isGlobal() { return project.getRmProject().isGlobal(); }
@Property
public boolean isShared() {
return project.getRMProject().isShared();
return project.getRmProject().isShared();
}
@Property
@@ -74,7 +72,7 @@ public class WebProjectInfo {
@Property
public boolean isCanEditDataSources() {
if (project.getRMProject().getType() == RMProjectType.USER && !customPrivateConnectionsEnabled) {
if (project.getRmProject().getType() == RMProjectType.USER && !customPrivateConnectionsEnabled) {
return false;
}
return hasDataSourcePermission(RMProjectPermission.DATA_SOURCES_EDIT);
@@ -96,12 +94,12 @@ public class WebProjectInfo {
}
private boolean hasDataSourcePermission(RMProjectPermission permission) {
return SMUtils.hasProjectPermission(session, project.getRMProject(), permission);
return SMUtils.hasProjectPermission(session, project.getRmProject(), permission);
}
@Property
public RMResourceType[] getResourceTypes() {
RMResourceType[] resourceTypes = project.getRMProject().getResourceTypes();
RMResourceType[] resourceTypes = project.getRmProject().getResourceTypes();
if(resourceTypes == null) {
return ArrayUtils.toArray(RMResourceType.class, new ArrayList<>());
@@ -17,8 +17,8 @@
package io.cloudbeaver.model;
import io.cloudbeaver.DBWebException;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.sql.SQLState;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -28,18 +28,22 @@ import java.io.StringWriter;
*/
public class WebServerError {
private final String message;
private final String stackTrace;
private final String errorType;
private final String errorCode;
private String message;
private String stackTrace;
private String errorType;
private String errorCode;
public WebServerError(Throwable ex) {
this.message = ex.getMessage();
StringWriter buf = new StringWriter();
ex.printStackTrace(new PrintWriter(buf, true));
this.stackTrace = buf.toString();
errorCode = String.valueOf(SQLState.getCodeFromException(ex));
errorType = ex instanceof DBQuotaException ? DBWebException.ERROR_CODE_QUOTA_EXCEEDED : null;
if (ex instanceof DBException) {
errorCode = String.valueOf(((DBException) ex).getErrorCode());
}
if (ex instanceof DBQuotaException) {
errorType = DBWebException.ERROR_CODE_QUOTA_EXCEEDED;
}
}
public String getMessage() {
@@ -0,0 +1,144 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.app;
import com.google.gson.annotations.Expose;
import io.cloudbeaver.auth.provider.local.LocalAuthProviderConstants;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebAuthProviderRegistry;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.utils.ArrayUtils;
import java.util.*;
public abstract class BaseAuthWebAppConfiguration extends BaseWebAppConfiguration implements WebAuthConfiguration {
private String defaultAuthProvider;
private String[] enabledAuthProviders;
private final Set<SMAuthProviderCustomConfiguration> authConfigurations;
// Legacy auth configs, left for backward compatibility
@Expose(serialize = false)
private final Map<String, SMAuthProviderCustomConfiguration> authConfiguration;
public BaseAuthWebAppConfiguration() {
super();
this.defaultAuthProvider = LocalAuthProviderConstants.PROVIDER_ID;
this.enabledAuthProviders = null;
this.authConfigurations = new LinkedHashSet<>();
this.authConfiguration = new LinkedHashMap<>();
}
public BaseAuthWebAppConfiguration(BaseAuthWebAppConfiguration src) {
super(src);
this.defaultAuthProvider = src.defaultAuthProvider;
this.enabledAuthProviders = src.enabledAuthProviders;
this.authConfigurations = new LinkedHashSet<>(src.authConfigurations);
this.authConfiguration = new LinkedHashMap<>(src.authConfiguration);
}
@Override
public String getDefaultAuthProvider() {
return defaultAuthProvider;
}
public void setDefaultAuthProvider(String defaultAuthProvider) {
this.defaultAuthProvider = defaultAuthProvider;
}
@Override
public String[] getEnabledAuthProviders() {
if (enabledAuthProviders == null) {
// No config - enable all providers (+backward compatibility)
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream().map(WebAuthProviderDescriptor::getId).toArray(String[]::new);
}
return enabledAuthProviders;
}
public void setEnabledAuthProviders(String[] enabledAuthProviders) {
this.enabledAuthProviders = enabledAuthProviders;
}
@Override
public boolean isAuthProviderEnabled(String id) {
var authProviderDescriptor = WebAuthProviderRegistry.getInstance().getAuthProvider(id);
if (authProviderDescriptor == null) {
return false;
}
if (!ArrayUtils.contains(getEnabledAuthProviders(), id)) {
return false;
}
if (!ArrayUtils.isEmpty(authProviderDescriptor.getRequiredFeatures())) {
for (String rf : authProviderDescriptor.getRequiredFeatures()) {
if (!isFeatureEnabled(rf)) {
return false;
}
}
}
return true;
}
////////////////////////////////////////////
// Auth provider configs
@Override
public Set<SMAuthProviderCustomConfiguration> getAuthCustomConfigurations() {
return authConfigurations;
}
@Override
@Nullable
public SMAuthProviderCustomConfiguration getAuthProviderConfiguration(@NotNull String id) {
synchronized (authConfigurations) {
return authConfigurations.stream().filter(c -> c.getId().equals(id)).findAny().orElse(null);
}
}
public void addAuthProviderConfiguration(@NotNull SMAuthProviderCustomConfiguration config) {
synchronized (authConfigurations) {
authConfigurations.removeIf(c -> c.getId().equals(config.getId()));
authConfigurations.add(config);
}
}
public void setAuthProvidersConfigurations(Collection<SMAuthProviderCustomConfiguration> authProviders) {
synchronized (authConfigurations) {
authConfigurations.clear();
authConfigurations.addAll(authProviders);
}
}
public boolean deleteAuthProviderConfiguration(@NotNull String id) {
synchronized (authConfigurations) {
return authConfigurations.removeIf(c -> c.getId().equals(id));
}
}
public void loadLegacyCustomConfigs() {
// Convert legacy map of configs into list
if (!authConfiguration.isEmpty()) {
for (Map.Entry<String, SMAuthProviderCustomConfiguration> entry : authConfiguration.entrySet()) {
entry.getValue().setId(entry.getKey());
authConfigurations.add(entry.getValue());
}
authConfiguration.clear();
}
}
}
@@ -18,16 +18,13 @@ package io.cloudbeaver.model.app;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jkiss.code.NotNull;
/**
* Abstract class that contains methods for loading configuration with gson.
*/
public abstract class BaseServerConfigurationController<T extends WebServerConfiguration>
implements WebServerConfigurationController<T> {
public abstract class BaseServerConfigurationController<T extends WebServerConfiguration> implements WebServerConfigurationController<T> {
@NotNull
public Gson getGson() {
protected Gson getGson() {
return getGsonBuilder().create();
}
@@ -33,14 +33,11 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
protected boolean resourceManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
protected String[] disabledBetaFeatures;
public BaseWebAppConfiguration() {
this.plugins = new LinkedHashMap<>();
this.resourceManagerEnabled = true;
this.enabledFeatures = null;
this.disabledBetaFeatures = new String[0];
this.showReadOnlyConnectionInfo = false;
}
@@ -49,7 +46,6 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
this.defaultUserTeam = src.defaultUserTeam;
this.resourceManagerEnabled = src.resourceManagerEnabled;
this.enabledFeatures = src.enabledFeatures;
this.disabledBetaFeatures = src.disabledBetaFeatures;
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
}
@@ -108,8 +104,4 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
public boolean isShowReadOnlyConnectionInfo() {
return showReadOnlyConnectionInfo;
}
public String[] getDisabledBetaFeatures() {
return disabledBetaFeatures;
}
}
@@ -16,7 +16,13 @@
*/
package io.cloudbeaver.model.app;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.model.log.SLF4JLogHandler;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebGlobalWorkspace;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.app.IApplicationContext;
import org.jkiss.code.NotNull;
@@ -24,16 +30,17 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import org.jkiss.dbeaver.registry.BaseApplicationImpl;
import org.jkiss.dbeaver.runtime.IVariableResolver;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
@@ -54,9 +61,14 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
public static final String CLI_PARAM_WEB_CONFIG = "-web-config";
public static final String LOGBACK_FILE_NAME = "logback.xml";
private static final Log log = Log.getLog(BaseWebApplication.class);
private String instanceId;
@NotNull
@Override
public DBPWorkspace createWorkspace(@NotNull DBPPlatform platform, @NotNull IWorkspace eclipseWorkspace) {
return new WebGlobalWorkspace(platform, eclipseWorkspace);
}
@Override
public RMController createResourceController(
@@ -90,7 +102,16 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
protected boolean loadServerConfiguration() throws DBException {
Path configFilePath = getMainConfigurationFilePath().toAbsolutePath();
Path configFolder = configFilePath.getParent();
// Configure logging
Path logbackConfigPath = getLogbackConfigPath(configFolder);
if (logbackConfigPath == null) {
System.err.println("Can't find slf4j configuration file in " + configFilePath.getParent());
} else {
System.setProperty("logback.configurationFile", logbackConfigPath.toString());
}
Log.setLogHandler(new SLF4JLogHandler());
// Load config file
@@ -155,6 +176,19 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return Files.exists(customConfigPath) ? customConfigPath : configPath.resolve(fileName);
}
@Override
public WebProjectImpl createProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project,
@NotNull DataSourceFilter dataSourceFilter
) {
return new WebSessionProjectImpl(
webSession,
project,
dataSourceFilter
);
}
/**
* There is no secret controller in base web app.
* Method returns VoidSecretController instance.
@@ -173,9 +207,7 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
}
@SuppressWarnings("unchecked")
public static void patchConfigurationWithProperties(
Map<String, Object> configProps, IVariableResolver varResolver
) {
public static void patchConfigurationWithProperties(Map<String, Object> configProps, IVariableResolver varResolver) {
for (Map.Entry<String, Object> entry : configProps.entrySet()) {
Object propValue = entry.getValue();
if (propValue instanceof String) {
@@ -210,27 +242,14 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
protected abstract void startServer() throws DBException;
@Override
public synchronized String getApplicationInstanceId() throws DBException {
if (instanceId == null) {
try {
byte[] macAddress = RuntimeUtils.getLocalMacAddress();
instanceId = String.join(
"_",
ApplicationRegistry.getInstance().getApplication().getId(),
getWorkspaceIdProperty(), // workspace id is read from property file
CommonUtils.toHexString(macAddress),
CommonUtils.toString(getServerPort())
);
} catch (Exception e) {
throw new DBException("Error during generation instance id generation", e);
}
public String getApplicationInstanceId() throws DBException {
try {
byte[] macAddress = RuntimeUtils.getLocalMacAddress();
String appId = ApplicationRegistry.getInstance().getApplication().getId();
return appId + "_" + CommonUtils.toHexString(macAddress) + getServerPort();
} catch (Exception e) {
throw new DBException("Error during generation instance id generation", e);
}
return instanceId;
}
@NotNull
public String getWorkspaceIdProperty() throws DBException {
return BaseWorkspaceImpl.readWorkspaceIdProperty();
}
public String getApplicationId() {
@@ -16,6 +16,9 @@
*/
package io.cloudbeaver.model.app;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBFileController;
@@ -24,13 +27,13 @@ import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import java.nio.file.Path;
import java.util.Map;
/**
* Base interface for web application
@@ -38,10 +41,6 @@ import java.util.Map;
public interface WebApplication extends DBPApplication {
boolean isConfigurationMode();
default boolean isInitializationMode() {
return false;
}
WebAppConfiguration getAppConfiguration();
WebServerConfiguration getServerConfiguration();
@@ -54,6 +53,12 @@ public interface WebApplication extends DBPApplication {
boolean isMultiNode();
WebProjectImpl createProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project,
@NotNull DataSourceFilter dataSourceFilter
);
SMController createSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException;
SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException;
@@ -90,8 +95,4 @@ public interface WebApplication extends DBPApplication {
int getServerPort();
boolean isLicenseRequired();
default void getStatusInfo(Map<String, Object> infoMap) {
}
}
@@ -16,12 +16,9 @@
*/
package io.cloudbeaver.model.app;
import com.google.gson.Gson;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import java.nio.file.Path;
import java.util.Map;
/**
* Server configuration controller.
@@ -34,11 +31,4 @@ public interface WebServerConfigurationController<T extends WebServerConfigurati
*/
void loadServerConfiguration(Path configPath) throws DBException;
@NotNull
default Map<String, Object> getOriginalConfigurationProperties() {
return Map.of();
}
@NotNull
Gson getGson();
}
@@ -1,18 +1,18 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp
*
* 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
* All Rights Reserved.
*
* 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.
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.model.rm;
@@ -20,6 +20,7 @@ package io.cloudbeaver.model.rm;
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.DBIcon;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.DBPObject;
@@ -36,6 +37,7 @@ import java.util.ArrayList;
import java.util.List;
public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
private static final Log log = Log.getLog(DBNResourceManagerProject.class);
private final RMProject project;
@@ -91,8 +93,8 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
}
@Override
public DBNResourceManagerResource[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (children == null && !monitor.isForceCacheUsage()) {
public DBNResourceManagerResource[] getChildren(DBRProgressMonitor monitor) throws DBException {
if (children == null) {
List<DBNResourceManagerResource> rfList = new ArrayList<>();
for (RMResource resource : getResourceController().listResources(
project.getId(), null, null, true, false, false)) {
@@ -121,15 +123,21 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
return this;
}
@Nullable
@Override
public DBPProject getOwnerProjectOrNull() {
public String toString() {
return getNodeDisplayName();
}
@Override
public DBPProject getOwnerProject() {
List<? extends DBPProject> globalProjects = getModel().getModelProjects();
if (globalProjects != null) {
for (DBPProject modelProject : globalProjects) {
if (CommonUtils.equalObjects(modelProject.getId(), project.getId())) {
return modelProject;
}
if (globalProjects == null) {
return null;
}
for (DBPProject modelProject : globalProjects) {
if (CommonUtils.equalObjects(modelProject.getId(), project.getId())) {
return modelProject;
}
}
return null;
@@ -140,10 +148,4 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
public DBPObject getObjectDetails(@NotNull DBRProgressMonitor monitor, @NotNull SMSessionContext sessionContext, @NotNull Object dataSource) throws DBException {
return project;
}
@Override
public String toString() {
return getNodeDisplayName();
}
}
@@ -1,18 +1,18 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp
*
* 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
* All Rights Reserved.
*
* 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.
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.model.rm;
@@ -110,8 +110,8 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
}
@Override
public DBNNode[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (children == null && !monitor.isForceCacheUsage()) {
public DBNNode[] getChildren(DBRProgressMonitor monitor) throws DBException {
if (children == null) {
List<DBNResourceManagerResource> rfList = new ArrayList<>();
for (RMResource resource : getResourceController().listResources(
getResourceProject().getId(), getResourceFolder(), null, true, false, false)) {
@@ -203,10 +203,9 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
return resource;
}
@Nullable
@Override
public DBPProject getOwnerProjectOrNull() {
return getParentNode().getOwnerProjectOrNull();
public DBPProject getOwnerProject() {
return getParentNode().getOwnerProject();
}
public RMResource getResource() {
@@ -1,25 +1,24 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp
*
* 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
* All Rights Reserved.
*
* 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.
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.model.rm;
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.Log;
import org.jkiss.dbeaver.model.DBPHiddenObject;
@@ -82,8 +81,8 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
}
@Override
public DBNResourceManagerProject[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (projects == null && !monitor.isForceCacheUsage()) {
public DBNResourceManagerProject[] getChildren(DBRProgressMonitor monitor) throws DBException {
if (projects == null) {
List<? extends DBPProject> projectList = getParentNode().getModel().getModelProjects();
if (CommonUtils.isEmpty(projectList)) {
return new DBNResourceManagerProject[0];
@@ -155,13 +154,8 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
return;
}
var projectNode = getProjectNode(project);
var rmResourcePath = Arrays.stream(resourcePath.split("/"))
.filter(CommonUtils::isNotEmpty)
.toList();
projectNode.ifPresent(
dbnResourceManagerProject -> dbnResourceManagerProject.removeChildResourceNode(new ArrayDeque<>(
rmResourcePath))
);
var rmResourcePath = Arrays.asList(resourcePath.split("/"));
projectNode.ifPresent(dbnResourceManagerProject -> dbnResourceManagerProject.removeChildResourceNode(new ArrayDeque<>(rmResourcePath)));
}
@NotNull
@@ -37,7 +37,6 @@ import org.jkiss.dbeaver.model.app.DBPProject;
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.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
@@ -131,7 +130,12 @@ public class LocalResourceController implements RMController {
if (project == null || refresh) {
SessionContextImpl sessionContext = new SessionContextImpl(null);
RMProject rmProject = makeProjectFromId(projectId, false);
project = new InternalWebProjectImpl(sessionContext, rmProject);
project = new BaseWebProjectImpl(
workspace,
this,
sessionContext,
rmProject,
(container) -> true);
projectRegistries.put(projectId, project);
}
return project;
@@ -252,20 +256,18 @@ public class LocalResourceController implements RMController {
return new RMProject[0];
}
var projects = new ArrayList<RMProject>();
try (Stream<Path> list = Files.list(sharedProjectsPath)) {
var allPaths = list.toList();
for (Path path : allPaths) {
var projectPerms = getProjectPermissions(
makeProjectIdFromPath(path, RMProjectType.SHARED),
RMProjectType.SHARED
);
var rmProject = makeProjectFromPath(path, projectPerms, RMProjectType.SHARED, false);
projects.add(rmProject);
}
return projects.stream()
.filter(Objects::nonNull)
.toArray(RMProject[]::new);
var allPaths = Files.list(sharedProjectsPath).collect(Collectors.toList());
for (Path path : allPaths) {
var projectPerms = getProjectPermissions(
makeProjectIdFromPath(path, RMProjectType.SHARED),
RMProjectType.SHARED
);
var rmProject = makeProjectFromPath(path, projectPerms, RMProjectType.SHARED, false);
projects.add(rmProject);
}
return projects.stream()
.filter(Objects::nonNull)
.toArray(RMProject[]::new);
} catch (IOException e) {
throw new DBException("Error reading shared projects", e);
}
@@ -579,7 +581,7 @@ public class LocalResourceController implements RMController {
@NotNull String resourcePath,
boolean isFolder
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "createResource")) {
try (var projectLock = lockController.lockProject(projectId, "createResource")) {
validateResourcePath(resourcePath);
Path targetPath = getTargetPath(projectId, resourcePath);
if (Files.exists(targetPath)) {
@@ -611,7 +613,7 @@ public class LocalResourceController implements RMController {
@NotNull String oldResourcePath,
@NotNull String newResourcePath
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "moveResource")) {
try (var projectLock = lockController.lockProject(projectId, "moveResource")) {
var normalizedOldResourcePath = CommonUtils.normalizeResourcePath(oldResourcePath);
var normalizedNewResourcePath = CommonUtils.normalizeResourcePath(newResourcePath);
if (log.isDebugEnabled()) {
@@ -626,9 +628,6 @@ public class LocalResourceController implements RMController {
}
Path newTargetPath = getTargetPath(projectId, normalizedNewResourcePath);
validateResourcePath(newTargetPath.toString());
if (Files.exists(newTargetPath)) {
throw new DBException("Resource with name %s already exists".formatted(newTargetPath.getFileName()));
}
try {
Files.move(oldTargetPath, newTargetPath);
} catch (IOException e) {
@@ -680,10 +679,11 @@ public class LocalResourceController implements RMController {
@Override
public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "deleteResource")) {
try (var projectLock = lockController.lockProject(projectId, "deleteResource")) {
if (log.isDebugEnabled()) {
log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : ""));
}
validateResourcePath(resourcePath);
Path targetPath = getTargetPath(projectId, resourcePath);
doFileWriteOperation(projectId, targetPath, () -> {
if (!Files.exists(targetPath)) {
@@ -768,7 +768,7 @@ public class LocalResourceController implements RMController {
@NotNull byte[] data,
boolean forceOverwrite
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "setResourceContents")) {
try (var lock = lockController.lockProject(projectId, "setResourceContents")) {
validateResourcePath(resourcePath);
Number fileSizeLimit = WebAppUtils.getWebApplication()
.getAppConfiguration()
@@ -822,7 +822,7 @@ public class LocalResourceController implements RMController {
@NotNull String propertyName,
@Nullable Object propertyValue
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "resourcePropertyUpdate")) {
try (var projectLock = lockController.lockProject(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -836,27 +836,6 @@ public class LocalResourceController implements RMController {
}
}
@NotNull
@Override
public String setResourceProperties(
@NotNull String projectId,
@NotNull String resourcePath,
@NotNull Map<String, Object> properties
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource '" + resourcePath + "' properties in project '" + projectId + "'");
webProject.setResourceProperties(resourcePath, properties);
return null;
}
);
return DEFAULT_CHANGE_ID;
}
}
private void validateResourcePath(String resourcePath) throws DBException {
var fullPath = Paths.get(resourcePath);
for (Path path : fullPath) {
@@ -1110,7 +1089,7 @@ public class LocalResourceController implements RMController {
);
}
if (readProperties) {
final BaseProjectImpl project = getWebProject(projectId, true);
final BaseProjectImpl project = (BaseProjectImpl) getWebProject(projectId, true);
final String resourcePath = getProjectRelativePath(projectId, path);
final Map<String, Object> properties = project.getResourceProperties(resourcePath);
@@ -1155,7 +1134,7 @@ public class LocalResourceController implements RMController {
);
}
private void fireRmProjectAddEvent(@NotNull RMProject project) {
private void fireRmProjectAddEvent(@NotNull RMProject project) throws DBException {
RMEventManager.fireEvent(
new RMEvent(
RMEvent.Action.RESOURCE_ADD,
@@ -1285,20 +1264,4 @@ public class LocalResourceController implements RMController {
}
private class InternalWebProjectImpl extends BaseWebProjectImpl {
public InternalWebProjectImpl(SessionContextImpl sessionContext, RMProject rmProject) {
super(
LocalResourceController.this.workspace,
LocalResourceController.this,
sessionContext,
rmProject);
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new DataSourceRegistry(this);
}
}
}
@@ -51,14 +51,14 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
@NotNull
protected final WebUserContext userContext;
@NotNull
protected final WebApplication application;
protected final WebAuthApplication application;
protected volatile long lastAccessTime;
private final List<CBWebSessionEventHandler> sessionEventHandlers = new CopyOnWriteArrayList<>();
private WebSessionEventsFilter eventsFilter = new WebSessionEventsFilter();
private final WebSessionWorkspace workspace;
public BaseWebSession(@NotNull String id, @NotNull WebApplication application) throws DBException {
public BaseWebSession(@NotNull String id, @NotNull WebAuthApplication application) throws DBException {
this.id = id;
this.application = application;
this.createTime = System.currentTimeMillis();
@@ -72,7 +72,6 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
return new WebUserContext(this.application, this.workspace);
}
@NotNull
public WebSessionWorkspace getWorkspace() {
return workspace;
}
@@ -174,24 +173,14 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
@Override
public void close() {
super.close();
cleanUpSession(true);
}
public void close(boolean clearTokens, boolean sendSessionExpiredEvent) {
cleanUpSession(sendSessionExpiredEvent);
}
private void cleanUpSession(boolean sendSessionExpiredEvent) {
var sessionExpiredEvent = new WSSessionExpiredEvent();
application.getEventController().addEvent(new WSEventDeleteTempFile(getSessionId()));
synchronized (sessionEventHandlers) {
var sessionExpiredEvent = new WSSessionExpiredEvent();
for (CBWebSessionEventHandler sessionEventHandler : sessionEventHandlers) {
if (sendSessionExpiredEvent) {
try {
sessionEventHandler.handleWebSessionEvent(sessionExpiredEvent);
} catch (DBException e) {
log.warn("Failed to send session expiration event", e);
}
try {
sessionEventHandler.handleWebSessionEvent(sessionExpiredEvent);
} catch (DBException e) {
log.warn("Failed to send session expiration event", e);
}
sessionEventHandler.close();
}
@@ -233,9 +222,6 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
@Property
public long getRemainingTime() {
if (application instanceof WebAuthApplication authApplication) {
return authApplication.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
}
return Integer.MAX_VALUE;
return application.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
}
}
@@ -1,18 +1,18 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp
*
* 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
* All Rights Reserved.
*
* 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.
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.model.session;
@@ -1,57 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.session;
import jakarta.servlet.http.HttpServletRequest;
public class WebHttpRequestInfo {
private final String id;
private final Object locale;
private final String lastRemoteAddress;
private final String lastRemoteUserAgent;
public WebHttpRequestInfo(HttpServletRequest request) {
this.id = request.getSession().getId();
this.locale = request.getAttribute("locale");
this.lastRemoteAddress = request.getRemoteAddr();
this.lastRemoteUserAgent = request.getHeader("User-Agent");
}
public WebHttpRequestInfo(String id, Object locale, String lastRemoteAddress, String lastRemoteUserAgent) {
this.id = id;
this.locale = locale;
this.lastRemoteAddress = lastRemoteAddress;
this.lastRemoteUserAgent = lastRemoteUserAgent;
}
public String getId() {
return id;
}
public Object getLocale() {
return locale;
}
public String getLastRemoteAddress() {
return lastRemoteAddress;
}
public String getLastRemoteUserAgent() {
return lastRemoteUserAgent;
}
}
@@ -19,18 +19,21 @@ package io.cloudbeaver.model.session;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebServerMessage;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.service.DBWSessionHandler;
import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -40,8 +43,12 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPEvent;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.auth.*;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.exec.DBCException;
@@ -49,7 +56,6 @@ import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
@@ -61,11 +67,15 @@ import org.jkiss.dbeaver.model.runtime.ProxyProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.security.user.SMObjectPermissions;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.websocket.event.MessageType;
import org.jkiss.dbeaver.model.websocket.event.WSEventType;
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
@@ -78,7 +88,6 @@ import java.util.stream.Collectors;
* Web session.
* Is the main source of data in web application
*/
//TODO: split to authenticated and non authenticated context
public class WebSession extends BaseWebSession
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, IAdaptable {
@@ -95,10 +104,12 @@ public class WebSession extends BaseWebSession
private String lastRemoteAddr;
private String lastRemoteUserAgent;
private Set<String> accessibleConnectionIds = Collections.emptySet();
private String locale;
private boolean cacheExpired;
protected WebSessionGlobalProjectImpl globalProject;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
@@ -112,34 +123,21 @@ public class WebSession extends BaseWebSession
private final Map<String, DBWSessionHandler> sessionHandlers;
public WebSession(
@NotNull WebHttpRequestInfo requestInfo,
@NotNull HttpServletRequest request,
@NotNull WebAuthApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers
) throws DBException {
this(requestInfo.getId(),
CommonUtils.toString(requestInfo.getLocale()),
application,
sessionHandlers
);
updateSessionParameters(requestInfo);
}
protected WebSession(
@NotNull String id,
@Nullable String locale,
@NotNull WebApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers
) throws DBException {
super(id, application);
super(request.getSession().getId(), application);
this.lastAccessTime = this.createTime;
setLocale(CommonUtils.toString(request.getSession().getAttribute(ATTR_LOCALE), this.locale));
this.sessionHandlers = sessionHandlers;
setLocale(CommonUtils.toString(locale, this.locale));
//force authorization of anonymous session to avoid access error,
//because before authorization could be called by any request,
//but now 'updateInfo' is called only in special requests,
//and the order of requests is not guaranteed.
//look at CB-4747
refreshSessionAuth();
updateSessionParameters(request);
}
@Nullable
@@ -153,8 +151,8 @@ public class WebSession extends BaseWebSession
}
}
@Nullable
public WebSessionProjectImpl getSingletonProject() {
@NotNull
public DBPProject getSingletonProject() {
return getWorkspace().getActiveProject();
}
@@ -251,6 +249,69 @@ public class WebSession extends BaseWebSession
initNavigatorModel();
}
/**
* updates data sources based on event in web session
*
* @param project project of connection
* @param dataSourceIds list of updated connections
* @param type type of event
*/
public synchronized boolean updateProjectDataSources(
DBPProject project,
List<String> dataSourceIds,
WSEventType type
) {
var sendDataSourceUpdatedEvent = false;
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
// save old connections
var oldDataSources = dataSourceIds.stream()
.map(registry::getDataSource)
.filter(Objects::nonNull)
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
ds -> new DataSourceDescriptor((DataSourceDescriptor) ds, ds.getRegistry())
));
if (type == WSEventType.DATASOURCE_CREATED || type == WSEventType.DATASOURCE_UPDATED) {
registry.refreshConfig(dataSourceIds);
}
for (String dsId : dataSourceIds) {
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
if (ds == null) {
continue;
}
switch (type) {
case DATASOURCE_CREATED -> {
WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds);
this.connections.put(getConnectionId(ds), connectionInfo);
sendDataSourceUpdatedEvent = true;
}
case DATASOURCE_UPDATED -> // if settings were changed we need to send event
sendDataSourceUpdatedEvent |= !ds.equalSettings(oldDataSources.get(dsId));
case DATASOURCE_DELETED -> {
WebDataSourceUtils.disconnectDataSource(this, ds);
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
dsrc.removeDataSourceFromList(ds);
}
this.connections.remove(getConnectionId(ds));
sendDataSourceUpdatedEvent = true;
}
default -> {
}
}
}
return sendDataSourceUpdatedEvent;
}
@NotNull
private String getConnectionId(@NotNull DBPDataSourceContainer container) {
return getConnectionId(container.getProject().getId(), container.getId());
}
@NotNull
private String getConnectionId(@NotNull String projectId, @NotNull String dsId) {
return projectId + ":" + dsId;
}
// Note: for admin use only
public synchronized void resetUserState() throws DBException {
clearAuthTokens();
@@ -271,7 +332,7 @@ public class WebSession extends BaseWebSession
this.navigatorModel.dispose();
this.navigatorModel = null;
}
this.globalProject = null;
this.connections.clear();
loadProjects();
@@ -291,6 +352,7 @@ public class WebSession extends BaseWebSession
// No anonymous mode in distributed apps
return;
}
refreshAccessibleConnectionIds();
try {
RMController controller = getRmController();
RMProject[] rmProjects = controller.listAccessibleProjects();
@@ -310,28 +372,57 @@ public class WebSession extends BaseWebSession
}
}
private WebSessionProjectImpl createWebProject(RMProject project) {
WebSessionProjectImpl sessionProject;
if (project.isGlobal()) {
sessionProject = createGlobalProject(project);
} else {
sessionProject = new WebSessionProjectImpl(this, project);
}
public WebProjectImpl createWebProject(RMProject project) {
// Do not filter data sources from user project
DataSourceFilter filter = project.getType() == RMProjectType.GLOBAL
? this::isDataSourceAccessible
: x -> true;
WebProjectImpl sessionProject = application.createProjectImpl(this, project, filter);
// do not load data sources for anonymous project
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
sessionProject.setInMemory(true);
}
DBPDataSourceRegistry dataSourceRegistry = sessionProject.getDataSourceRegistry();
dataSourceRegistry.setAuthCredentialsProvider(this);
addSessionProject(sessionProject);
if (!project.isShared() || application.isConfigurationMode()) {
getWorkspace().setActiveProject(sessionProject);
}
for (DBPDataSourceContainer ds : dataSourceRegistry.getDataSources()) {
addConnection(new WebConnectionInfo(this, ds));
}
Throwable lastError = dataSourceRegistry.getLastError();
if (lastError != null) {
addSessionError(lastError);
log.error("Error refreshing connections from project '" + project.getId() + "'", lastError);
}
return sessionProject;
}
protected WebSessionProjectImpl createGlobalProject(RMProject project) {
globalProject = new WebSessionGlobalProjectImpl(this, project);
globalProject.refreshAccessibleConnectionIds();
return globalProject;
public void filterAccessibleConnections(List<WebConnectionInfo> connections) {
connections.removeIf(c -> !isDataSourceAccessible(c.getDataSourceContainer()));
}
private boolean isDataSourceAccessible(DBPDataSourceContainer dataSource) {
return dataSource.isExternallyProvided() ||
dataSource.isTemporary() ||
this.hasPermission(DBWConstants.PERMISSION_ADMIN) ||
accessibleConnectionIds.contains(dataSource.getId());
}
@NotNull
private Set<String> readAccessibleConnectionIds() {
try {
return getSecurityController()
.getAllAvailableObjectsPermissions(SMObjectType.datasource)
.stream()
.map(SMObjectPermissions::getObjectId)
.collect(Collectors.toSet());
} catch (DBException e) {
addSessionError(e);
log.error("Error reading connection grants", e);
return Collections.emptySet();
}
}
private void resetSessionCache() throws DBCException {
@@ -349,7 +440,17 @@ public class WebSession extends BaseWebSession
}
private void resetNavigationModel() {
getWorkspace().getProjects().forEach(WebSessionProjectImpl::dispose);
Map<String, WebConnectionInfo> conCopy;
synchronized (this.connections) {
conCopy = new HashMap<>(this.connections);
this.connections.clear();
}
for (WebConnectionInfo connectionInfo : conCopy.values()) {
if (connectionInfo.isConnected()) {
new DisconnectJob(connectionInfo.getDataSourceContainer()).schedule();
}
}
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
@@ -363,9 +464,7 @@ public class WebSession extends BaseWebSession
authAsAnonymousUser();
} else if (getUserId() != null) {
userContext.refreshPermissions();
if (globalProject != null) {
globalProject.refreshAccessibleConnectionIds();
}
refreshAccessibleConnectionIds();
}
} catch (Exception e) {
@@ -374,6 +473,32 @@ public class WebSession extends BaseWebSession
}
}
private synchronized void refreshAccessibleConnectionIds() {
this.accessibleConnectionIds = readAccessibleConnectionIds();
}
public synchronized void addAccessibleConnectionToCache(@NotNull String dsId) {
this.accessibleConnectionIds.add(dsId);
var registry = getProjectById(WebAppUtils.getGlobalProjectId()).getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
connections.put(getConnectionId(dataSource), new WebConnectionInfo(this, dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_ADD, dataSource, true));
}
}
public synchronized void removeAccessibleConnectionFromCache(@NotNull String dsId) {
var registry = getProjectById(WebAppUtils.getGlobalProjectId()).getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
this.accessibleConnectionIds.remove(dsId);
connections.remove(getConnectionId(dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_REMOVE, dataSource));
dataSource.dispose();
}
}
private synchronized void authAsAnonymousUser() throws DBException {
if (!application.getAppConfiguration().isAnonymousAccessEnabled()) {
@@ -432,12 +557,75 @@ public class WebSession extends BaseWebSession
}
}
public synchronized void updateSessionParameters(WebHttpRequestInfo requestInfo) {
this.lastRemoteAddr = requestInfo.getLastRemoteAddress();
this.lastRemoteUserAgent = requestInfo.getLastRemoteUserAgent();
public synchronized void updateSessionParameters(HttpServletRequest request) {
this.lastRemoteAddr = request.getRemoteAddr();
this.lastRemoteUserAgent = request.getHeader("User-Agent");
this.cacheExpired = false;
}
@Association
public List<WebConnectionInfo> getConnections() {
synchronized (connections) {
return new ArrayList<>(connections.values());
}
}
@NotNull
public WebConnectionInfo getWebConnectionInfo(@Nullable String projectId, String connectionID) throws DBWebException {
WebConnectionInfo connectionInfo = null;
synchronized (connections) {
if (projectId != null) {
connectionInfo = connections.get(getConnectionId(projectId, connectionID));
} else {
addWarningMessage("Project id is not defined in request. Try to find it from connection cache");
for (Map.Entry<String, WebConnectionInfo> entry : connections.entrySet()) {
String k = entry.getKey();
WebConnectionInfo v = entry.getValue();
if (k.contains(connectionID)) {
connectionInfo = v;
break;
}
}
}
}
if (connectionInfo == null) {
WebProjectImpl project = getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found in web workspace");
}
DBPDataSourceContainer dataSource = project.getDataSourceRegistry().getDataSource(connectionID);
if (dataSource != null) {
connectionInfo = new WebConnectionInfo(this, dataSource);
synchronized (connections) {
connections.put(getConnectionId(dataSource), connectionInfo);
}
} else {
throw new DBWebException("Connection '" + connectionID + "' not found");
}
}
return connectionInfo;
}
@Nullable
public WebConnectionInfo findWebConnectionInfo(String projectId, String connectionId) {
synchronized (connections) {
return connections.get(getConnectionId(projectId, connectionId));
}
}
public void addConnection(WebConnectionInfo connectionInfo) {
synchronized (connections) {
connections.put(getConnectionId(connectionInfo.getDataSourceContainer()), connectionInfo);
}
}
public void removeConnection(WebConnectionInfo connectionInfo) {
connectionInfo.clearCache();
synchronized (connections) {
connections.remove(getConnectionId(connectionInfo.getDataSourceContainer()));
}
}
@Override
public void close() {
try {
@@ -455,25 +643,6 @@ public class WebSession extends BaseWebSession
super.close();
}
@Override
public void close(boolean clearTokens, boolean sendSessionExpiredEvent) {
try {
resetNavigationModel();
resetSessionCache();
} catch (Throwable e) {
log.error(e);
}
if (clearTokens) {
try {
clearAuthTokens();
} catch (Exception e) {
log.error("Error closing web session tokens");
}
}
this.userContext.setUser(null);
super.close(clearTokens, sendSessionExpiredEvent);
}
private List<WebAuthInfo> clearAuthTokens() throws DBException {
ArrayList<WebAuthInfo> tokensCopy;
synchronized (authTokens) {
@@ -621,7 +790,7 @@ public class WebSession extends BaseWebSession
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
value = persistentAttribute.getValue();
}
return (T) value;
}
@@ -637,7 +806,7 @@ public class WebSession extends BaseWebSession
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
value = persistentAttribute.getValue();
}
if (value == null) {
value = creator.apply(null);
@@ -780,12 +949,9 @@ public class WebSession extends BaseWebSession
}
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
WebSessionProjectImpl project = getProjectById(dataSourceContainer.getProject().getId());
if (project != null) {
WebConnectionInfo webConnectionInfo = project.findWebConnectionInfo(dataSourceContainer.getId());
if (webConnectionInfo != null) {
WebDataSourceUtils.saveCredentialsInDataSource(webConnectionInfo, dataSourceContainer, configuration);
}
WebConnectionInfo webConnectionInfo = findWebConnectionInfo(dataSourceContainer.getProject().getId(), dataSourceContainer.getId());
if (webConnectionInfo != null) {
WebDataSourceUtils.saveCredentialsInDataSource(webConnectionInfo, dataSourceContainer, configuration);
}
// uncommented because we had the problem with non-native auth models
@@ -794,7 +960,7 @@ public class WebSession extends BaseWebSession
InstanceCreator<DBAAuthCredentials> credTypeAdapter = type -> credentials;
Gson credGson = new GsonBuilder()
.setStrictness(Strictness.LENIENT)
.setLenient()
.registerTypeAdapter(credentials.getClass(), credTypeAdapter)
.create();
@@ -876,17 +1042,12 @@ public class WebSession extends BaseWebSession
}
@Nullable
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
public WebProjectImpl getProjectById(@Nullable String projectId) {
return getWorkspace().getProjectById(projectId);
}
/**
* Returns project info from session cache.
*
* @throws DBWebException if project with provided id is not found.
*/
public WebSessionProjectImpl getAccessibleProjectById(@Nullable String projectId) throws DBWebException {
WebSessionProjectImpl project = null;
public WebProjectImpl getAccessibleProjectById(@Nullable String projectId) throws DBWebException {
WebProjectImpl project = null;
if (projectId != null) {
project = getWorkspace().getProjectById(projectId);
}
@@ -896,24 +1057,18 @@ public class WebSession extends BaseWebSession
return project;
}
public List<WebSessionProjectImpl> getAccessibleProjects() {
public List<WebProjectImpl> getAccessibleProjects() {
return getWorkspace().getProjects();
}
/**
* Adds project to session cache and navigator tree.
*/
public void addSessionProject(@NotNull WebSessionProjectImpl project) {
public void addSessionProject(@NotNull WebProjectImpl project) {
getWorkspace().addProject(project);
if (navigatorModel != null) {
navigatorModel.getRoot().addProject(project, false);
}
}
/**
* Removes project from session cache and navigator tree.
*/
public void deleteSessionProject(@Nullable WebSessionProjectImpl project) {
public void deleteSessionProject(@Nullable WebProjectImpl project) {
if (project != null) {
project.dispose();
}
@@ -938,6 +1093,10 @@ public class WebSession extends BaseWebSession
return;
}
deleteSessionProject(project);
var projectConnections = project.getDataSourceRegistry().getDataSources();
for (DBPDataSourceContainer c : projectConnections) {
removeConnection(new WebConnectionInfo(this, c));
}
}
@NotNull
@@ -949,16 +1108,6 @@ public class WebSession extends BaseWebSession
return project.getFileSystemManager();
}
@NotNull
public DBPPreferenceStore getUserPreferenceStore() {
return getUserContext().getPreferenceStore();
}
@Nullable
public WebSessionGlobalProjectImpl getGlobalProject() {
return globalProject;
}
private class SessionProgressMonitor extends BaseProgressMonitor {
@Override
public void beginTask(String name, int totalWork) {
@@ -993,6 +1142,15 @@ public class WebSession extends BaseWebSession
}
}
private record PersistentAttribute(Object value) {
private static class PersistentAttribute {
private final Object value;
public PersistentAttribute(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
}
@@ -50,9 +50,6 @@ public class WebSessionEventsFilter {
}
public boolean isEventAllowed(WSEvent event) {
if (event.isForceProcessed()) {
return true;
}
if (!subscribedEventTopics.isEmpty() && !subscribedEventTopics.contains(event.getTopicId())
) {
return false;
@@ -1,72 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.session;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.impl.preferences.AbstractUserPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import java.io.IOException;
import java.util.Map;
public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
public WebSessionPreferenceStore(
@NotNull DBPPreferenceStore parentStore
) {
super(parentStore);
}
@NotNull
public Map<String, Object> getCustomUserParameters() {
return userPreferences;
}
// to avoid redundant sm api call
public void updatePreferenceValues(@NotNull Map<String, Object> newValues) {
userPreferences.putAll(newValues);
}
@Override
protected void setUserPreference(String name, Object value) {
throw new RuntimeException("Not implemented");
}
@Override
public String getDefaultString(String name) {
return parentStore.getDefaultString(name);
}
@Override
public boolean isDefault(String name) {
return !userPreferences.containsKey(name) && parentStore.isDefault(name);
}
@Override
public boolean needsSaving() {
return false;
}
@Override
public void setToDefault(String name) {
throw new RuntimeException("Not implemented");
}
@Override
public void save() throws IOException {
throw new RuntimeException("Not implemented");
}
}
@@ -16,12 +16,11 @@
*/
package io.cloudbeaver.model.session;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.WebProjectImpl;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMUtils;
@@ -39,8 +38,8 @@ public class WebSessionWorkspace implements DBPWorkspace {
private final BaseWebSession session;
private final SessionContextImpl workspaceAuthContext;
private final List<WebSessionProjectImpl> accessibleProjects = new ArrayList<>();
private WebSessionProjectImpl activeProject;
private final List<WebProjectImpl> accessibleProjects = new ArrayList<>();
private WebProjectImpl activeProject;
public WebSessionWorkspace(BaseWebSession session) {
this.session = session;
@@ -82,20 +81,20 @@ public class WebSessionWorkspace implements DBPWorkspace {
@NotNull
@Override
public List<WebSessionProjectImpl> getProjects() {
public List<WebProjectImpl> getProjects() {
return accessibleProjects;
}
@Nullable
@Override
public WebSessionProjectImpl getActiveProject() {
public DBPProject getActiveProject() {
return activeProject;
}
@Nullable
@Override
public WebSessionProjectImpl getProject(@NotNull String projectName) {
for (WebSessionProjectImpl project : accessibleProjects) {
public WebProjectImpl getProject(@NotNull String projectName) {
for (WebProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
}
@@ -105,11 +104,11 @@ public class WebSessionWorkspace implements DBPWorkspace {
@Nullable
@Override
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
public WebProjectImpl getProjectById(@NotNull String projectId) {
if (projectId == null) {
return activeProject;
}
for (WebSessionProjectImpl project : accessibleProjects) {
for (WebProjectImpl project : accessibleProjects) {
if (project.getId().equals(projectId)) {
return project;
}
@@ -123,36 +122,26 @@ public class WebSessionWorkspace implements DBPWorkspace {
return workspaceAuthContext;
}
@Override
public void initializeProjects() {
// noop
}
@Override
public void dispose() {
clearProjects();
}
@Override
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
return null;
public void setActiveProject(DBPProject activeProject) {
this.activeProject = (WebProjectImpl) activeProject;
}
public void setActiveProject(WebSessionProjectImpl activeProject) {
this.activeProject = activeProject;
}
void addProject(WebSessionProjectImpl project) {
void addProject(WebProjectImpl project) {
accessibleProjects.add(project);
}
void removeProject(WebSessionProjectImpl project) {
void removeProject(WebProjectImpl project) {
accessibleProjects.remove(project);
}
void clearProjects() {
if (!this.accessibleProjects.isEmpty()) {
for (WebSessionProjectImpl project : accessibleProjects) {
for (WebProjectImpl project : accessibleProjects) {
project.dispose();
}
this.activeProject = null;
@@ -36,17 +36,18 @@ import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Web user context.
* Contains user state and services based on available permissions
*/
//TODO: split to authenticated and non authenticated context
public class WebUserContext implements SMCredentialsProvider {
private static final Log log = Log.getLog(WebUserContext.class);
@@ -65,7 +66,6 @@ public class WebUserContext implements SMCredentialsProvider {
private RMController rmController;
private DBFileController fileController;
private Set<String> accessibleProjectIds = new HashSet<>();
private final WebSessionPreferenceStore preferenceStore;
public WebUserContext(WebApplication application, DBPWorkspace workspace) throws DBException {
this.application = application;
@@ -73,7 +73,6 @@ public class WebUserContext implements SMCredentialsProvider {
this.securityController = application.createSecurityController(this);
this.rmController = application.createResourceController(this, workspace);
this.fileController = application.createFileController(this);
this.preferenceStore = new WebSessionPreferenceStore(DBWorkbench.getPlatform().getPreferenceStore());
setUserPermissions(getDefaultPermissions());
}
@@ -117,13 +116,7 @@ public class WebUserContext implements SMCredentialsProvider {
setRefreshToken(smRefreshToken);
setUserPermissions(smAuthPermissions.getPermissions());
this.adminSecurityController = application.getAdminSecurityController(this);
this.rmController = application.createResourceController(this, workspace);
if (isSessionChanged) {
if (smAuthPermissions.getUserId() != null) {
this.preferenceStore.updateAllUserPreferences(securityController.getCurrentUserParameters());
} else {
this.preferenceStore.updateAllUserPreferences(Map.of());
}
this.smSessionId = smAuthPermissions.getSessionId();
setUser(smAuthPermissions.getUserId() == null ? null : new WebUser(securityController.getCurrentUser()));
refreshAccessibleProjects();
@@ -258,8 +251,4 @@ public class WebUserContext implements SMCredentialsProvider {
private void setRefreshToken(@Nullable String refreshToken) {
this.refreshToken = refreshToken;
}
public WebSessionPreferenceStore getPreferenceStore() {
return preferenceStore;
}
}
@@ -70,7 +70,6 @@ public class WebUser {
return Collections.emptyMap();
}
@NotNull
public String[] getTeams() {
return user.getUserTeams();
}
@@ -80,7 +80,7 @@ public class WebAuthProviderConfiguration {
}
private String buildRedirectUrl(String baseUrl) {
return baseUrl + "?" + CBAuthConstants.CB_REDIRECT_URL_REQUEST_PARAM + "=" + WebAppUtils.getFullServerUrl();
return baseUrl + "?" + CBAuthConstants.CB_REDIRECT_URL_REQUEST_PARAM + "=" + WebAppUtils.getWebApplication().getServerURL();
}
@Property
@@ -109,14 +109,6 @@ public class WebAuthProviderConfiguration {
return instance instanceof SMAuthProviderFederated ? ((SMAuthProviderFederated) instance).getAcsLink(getId(), config.getParameters()) : null;
}
@Property
public String getEntityIdLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated
? ((SMAuthProviderFederated) instance).getEntityIdLink(getId(), config.getParameters())
: null;
}
@Override
public String toString() {
return getDisplayName();
@@ -1,147 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import io.cloudbeaver.DBWConstants;
import org.eclipse.core.runtime.Plugin;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.app.DBACertificateStorage;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.app.DefaultCertificateStorage;
import org.jkiss.dbeaver.model.qm.QMRegistry;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.registry.BasePlatformImpl;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.runtime.SecurityProviderUtils;
import org.jkiss.dbeaver.runtime.qm.QMLogFileWriter;
import org.jkiss.dbeaver.runtime.qm.QMRegistryImpl;
import org.jkiss.dbeaver.utils.ContentUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public abstract class BaseGQLPlatform extends BasePlatformImpl {
private static final Log log = Log.getLog(BaseGQLPlatform.class);
public static final String WORK_DATA_FOLDER_NAME = ".work-data";
private Path tempFolder;
private QMRegistryImpl queryManager;
private QMLogFileWriter qmLogWriter;
private DBACertificateStorage certificateStorage;
private WebGlobalWorkspace workspace;
@Override
protected synchronized void initialize() {
// Register BC security provider
SecurityProviderUtils.registerSecurityProvider();
// Register properties adapter
this.workspace = new WebGlobalWorkspace(this);
this.workspace.initializeProjects();
QMUtils.initApplication(this);
this.queryManager = new QMRegistryImpl();
this.qmLogWriter = new QMLogFileWriter();
this.queryManager.registerMetaListener(qmLogWriter);
this.certificateStorage = new DefaultCertificateStorage(
WebPlatformActivator.getInstance()
.getStateLocation()
.toFile()
.toPath()
.resolve(DBConstants.CERTIFICATE_STORAGE_FOLDER));
super.initialize();
}
@Override
protected Plugin getProductPlugin() {
return WebPlatformActivator.getInstance();
}
@NotNull
@Override
public DBACertificateStorage getCertificateStorage() {
return certificateStorage;
}
@NotNull
@Override
public DBPWorkspace getWorkspace() {
return workspace;
}
@NotNull
public Path getTempFolder(@NotNull DBRProgressMonitor monitor, @NotNull String name) {
if (tempFolder == null) {
// Make temp folder
monitor.subTask("Create temp folder");
tempFolder = workspace.getAbsolutePath().resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
}
if (!Files.exists(tempFolder)) {
try {
Files.createDirectories(tempFolder);
} catch (IOException e) {
log.error("Can't create temp directory " + tempFolder, e);
}
}
Path folder = tempFolder.resolve(name);
if (!Files.exists(folder)) {
try {
Files.createDirectories(folder);
} catch (IOException e) {
log.error("Error creating temp folder '" + folder.toAbsolutePath() + "'", e);
}
}
return folder;
}
@Override
public synchronized void dispose() {
super.dispose();
if (this.qmLogWriter != null) {
this.queryManager.unregisterMetaListener(qmLogWriter);
this.qmLogWriter.dispose();
this.qmLogWriter = null;
}
if (this.queryManager != null) {
this.queryManager.dispose();
//queryManager = null;
}
DataSourceProviderRegistry.dispose();
// Remove temp folder
if (tempFolder != null) {
if (!ContentUtils.deleteFileRecursive(tempFolder.toFile())) {
log.warn("Can't delete temp folder '" + tempFolder.toAbsolutePath() + "'");
}
tempFolder = null;
}
}
@NotNull
public QMRegistry getQueryManager() {
return queryManager;
}
}
@@ -1,85 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import java.nio.file.Path;
/**
* Web global project.
*/
public class WebGlobalProject extends BaseProjectImpl {
private static final Log log = Log.getLog(WebGlobalProject.class);
private final String projectName;
public WebGlobalProject(
@NotNull DBPWorkspace workspace,
@Nullable SMSessionContext sessionContext,
@NotNull String projectName
) {
super(workspace, sessionContext);
this.projectName = projectName;
}
@Override
public boolean isVirtual() {
return false;
}
@NotNull
@Override
public String getName() {
return projectName;
}
@NotNull
@Override
public Path getAbsolutePath() {
return getWorkspace().getAbsolutePath().resolve(projectName);
}
@Override
public boolean isOpen() {
return true;
}
@Override
public void ensureOpen() {
}
@Override
public boolean isUseSecretStorage() {
return false;
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new DataSourceRegistry(this);
}
}
@@ -16,102 +16,19 @@
*/
package io.cloudbeaver.server;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.utils.WebAppUtils;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.eclipse.core.resources.IWorkspace;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jkiss.dbeaver.registry.EclipseWorkspaceImpl;
/**
* Web global workspace.
* <p>
* Basically just a wrapper around Eclipse workspace.
*/
public class WebGlobalWorkspace extends BaseWorkspaceImpl {
public class WebGlobalWorkspace extends EclipseWorkspaceImpl {
private static final Log log = Log.getLog(WebGlobalWorkspace.class);
protected final Map<String, WebProjectImpl> projects = new LinkedHashMap<>();
private WebGlobalProject globalProject;
public WebGlobalWorkspace(DBPPlatform platform) {
super(platform,
platform.getApplication().isMultiuser()
? Path.of(getWorkspaceURI())
: ((WebApplication) platform.getApplication()).getWorkspaceDirectory());
public WebGlobalWorkspace(DBPPlatform platform, IWorkspace eclipseWorkspace) {
super(platform, eclipseWorkspace);
}
@NotNull
private static URI getWorkspaceURI() {
String workspacePath = Platform.getInstanceLocation().getURL().toString();
try {
return new URI(workspacePath);
} catch (URISyntaxException e) {
throw new IllegalStateException("Workspace path is invalid: " + workspacePath, e);
}
}
@Override
public void initializeProjects() {
initializeWorkspaceSession();
// Load global project
Path globalProjectPath = getAbsolutePath().resolve(WebAppUtils.getGlobalProjectId());
if (!Files.exists(globalProjectPath)) {
try {
Files.createDirectories(globalProjectPath);
} catch (IOException e) {
log.error("Error creating global project path: " + globalProject, e);
}
}
globalProject = new WebGlobalProject(
this,
getAuthContext(),
CommonUtils.notEmpty(WebAppUtils.getWebApplication().getDefaultProjectName())
);
activeProject = globalProject;
}
@NotNull
@Override
public String getWorkspaceId() {
return readWorkspaceIdProperty();
}
@Nullable
@Override
public DBPProject getActiveProject() {
return super.getActiveProject();
}
@NotNull
@Override
public List<BaseProjectImpl> getProjects() {
return Collections.singletonList(globalProject);
}
@Nullable
@Override
public BaseProjectImpl getProject(@NotNull String projectName) {
if (globalProject.getId().equals(projectName)) {
return globalProject;
}
return null;
}
}
@@ -16,6 +16,8 @@
*/
package io.cloudbeaver.server;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Plugin;
import org.jkiss.dbeaver.ModelPreferences;
import org.jkiss.dbeaver.model.impl.preferences.BundlePreferenceStore;
@@ -74,6 +76,13 @@ public class WebPlatformActivator extends Plugin {
return preferences;
}
/**
* Returns the workspace instance.
*/
public static IWorkspace getWorkspace() {
return ResourcesPlugin.getWorkspace();
}
protected void shutdownPlatform() {
}
@@ -23,9 +23,6 @@ import org.jkiss.dbeaver.DBException;
* Servlet service
*/
public interface DBWServiceBindingServlet<APPLICATION extends WebApplication> extends DBWServiceBinding {
default boolean isApplicable(WebApplication application) {
return true;
}
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
}
@@ -18,7 +18,6 @@ package io.cloudbeaver.service;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.app.WebServerConfiguration;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -32,7 +31,6 @@ public interface DBWServiceServerConfigurator extends DBWServiceBinding {
void configureServer(
@NotNull WebApplication application,
@Nullable WebSession session,
@NotNull WebServerConfiguration serverConfiguration,
@NotNull WebAppConfiguration appConfig
) throws DBException;
@@ -22,6 +22,7 @@ package io.cloudbeaver.service.sql;
public class WebSQLConstants {
public static final String QUOTA_PROP_ROW_LIMIT = "sqlResultSetRowsLimit";
public static final String QUOTA_PROP_MEMORY_LIMIT = "sqlResultSetMemoryLimit";
public static final String QUOTA_PROP_QUERY_LIMIT = "sqlMaxRunningQueries";
public static final String QUOTA_PROP_SQL_QUERY_TIMEOUT = "sqlQueryTimeout";
public static final String QUOTA_PROP_TEXT_PREVIEW_MAX_LENGTH = "sqlTextPreviewMaxLength";
@@ -22,7 +22,6 @@ import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
import org.jkiss.dbeaver.model.exec.trace.DBCTrace;
import org.jkiss.dbeaver.model.struct.*;
import java.util.HashSet;
@@ -39,7 +38,6 @@ public class WebSQLResultsInfo {
@NotNull
private final String id;
private DBDAttributeBinding[] attributes;
private DBCTrace trace;
private String queryText;
public WebSQLResultsInfo(@NotNull DBSDataContainer dataContainer, @NotNull String id) {
@@ -134,11 +132,4 @@ public class WebSQLResultsInfo {
(!(entity instanceof DBSDocumentContainer) && !entity.getDataSource().getInfo().isDynamicMetadata());
}
public DBCTrace getTrace() {
return trace;
}
public void setTrace(@NotNull DBCTrace trace) {
this.trace = trace;
}
}
@@ -1,124 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* GraphQL client for tests.
*/
public class WebGQLClient {
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
public static final String GQL_AUTHENTICATE = """
query authLogin($provider: ID!, $configuration: ID, $credentials: Object, $linkUser: Boolean, $forceSessionsLogout: Boolean) {
result: authLogin(
provider: $provider
configuration: $configuration
credentials: $credentials
linkUser: $linkUser
forceSessionsLogout: $forceSessionsLogout
) {
authId
authStatus
}
}""";
@NotNull
private final HttpClient httpClient;
@NotNull
private final String apiUrl;
public WebGQLClient(@NotNull HttpClient httpClient, @NotNull String apiUrl) {
this.httpClient = httpClient;
this.apiUrl = apiUrl;
}
/**
* Sends GraphQL request without additional headers
*
* @param query GraphQL query
* @param variables GraphQL query variables
* @return GraphQL response
*/
@NotNull
public <T> T sendQuery(@NotNull String query, @Nullable Map<String, Object> variables) throws Exception {
return sendQueryWithHeaders(query, variables, List.of());
}
/**
* Sends GraphQL request without additional headers
*
* @param query GraphQL query
* @param variables GraphQL query variables
* @param headers HTTP request headers
* @return GraphQL response
*/
@NotNull
public <T> T sendQueryWithHeaders(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull List<String> headers
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(makeGQLRequest(query, variables)))
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
.header("Content-Type", "application/json");
if (!headers.isEmpty()) {
requestBuilder.headers(headers.toArray(String[]::new));
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> body = gson.fromJson(
response.body(),
JSONUtils.MAP_TYPE_TOKEN
);
if (body.containsKey("errors")) {
String message = JSONUtils.getString(JSONUtils.getObjectList(body, "errors").get(0), "message");
throw new DBException(message);
}
// graphql response will be in "data" key
return (T) JSONUtils.getObject(body, "data").get("result");
}
@NotNull
private String makeGQLRequest(@NotNull String text, @Nullable Map<String, Object> variables) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("query", text);
if (variables != null && !variables.isEmpty()) {
request.put("variables", variables);
}
return gson.toJson(request, Map.class);
}
}
@@ -38,8 +38,6 @@ import org.jkiss.utils.CommonUtils;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class WebAppUtils {
private static final Log log = Log.getLog(WebAppUtils.class);
@@ -76,25 +74,17 @@ public class WebAppUtils {
}
@SuppressWarnings("unchecked")
public static Map<String, Object> mergeConfigurations(
Map<String, Object> priorityConfiguration,
Map<String, Object> additional
) {
public static Map<String, Object> mergeConfigurations(Map<String, Object> origin, Map<String, Object> additional) {
var resultConfig = new HashMap<String, Object>();
Set<String> rootKeys = new HashSet<>(priorityConfiguration.keySet());
Set<String> rootKeys = new HashSet<>(origin.keySet());
rootKeys.addAll(additional.keySet());
for (var rootKey : rootKeys) {
var originValue = priorityConfiguration.get(rootKey);
var originValue = origin.get(rootKey);
var additionalValue = additional.get(rootKey);
if (originValue == null || additionalValue == null) {
Object resultValue = null;
if (additional.containsKey(rootKey)) {
resultValue = additionalValue;
} else if (originValue != null) {
resultValue = originValue;
}
var resultValue = originValue != null ? originValue : additionalValue;
resultConfig.put(rootKey, resultValue);
continue;
}
@@ -140,10 +130,6 @@ public class WebAppUtils {
if (!(oldValue instanceof String)) {
return newValue;
}
//new value already contains variable pattern
if (newValue instanceof String newStringValue && GeneralUtils.isVariablePattern(newStringValue)) {
return newValue;
}
String value = (String) oldValue;
if (!GeneralUtils.isVariablePattern(value)) {
return newValue;
@@ -201,7 +187,7 @@ public class WebAppUtils {
path = path.concat("; SameSite=" + sameSite);
}
}
sessionCookie.setHttpOnly(true);
sessionCookie.setPath(path);
response.addCookie(sessionCookie);
}
@@ -271,13 +257,4 @@ public class WebAppUtils {
}
}
@NotNull
public static String getFullServerUrl() {
WebApplication application = WebAppUtils.getWebApplication();
return Stream.of(application.getServerURL(), application.getRootURI())
.map(WebAppUtils::removeSideSlashes)
.filter(CommonUtils::isNotEmpty)
.collect(Collectors.joining("/"));
}
}
@@ -18,9 +18,9 @@ package io.cloudbeaver.utils;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -37,7 +37,6 @@ import org.jkiss.utils.CommonUtils;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class WebDataSourceUtils {
@@ -98,10 +97,6 @@ public class WebDataSourceUtils {
private static void setSecureProperties(DBWHandlerConfiguration handlerConfig, WebNetworkHandlerConfigInput cfgInput, boolean ignoreNulls) {
var secureProperties = cfgInput.getSecureProperties();
if (secureProperties == null) {
if (!handlerConfig.isSavePassword()) {
// clear all secure properties from handler config
handlerConfig.setSecureProperties(Map.of());
}
return;
}
for (var pr : secureProperties.entrySet()) {
@@ -114,22 +109,14 @@ public class WebDataSourceUtils {
@Nullable
public static DBPDataSourceContainer getLocalOrGlobalDataSource(
WebSession webSession, @Nullable String projectId, String connectionId
WebApplication application, WebSession webSession, @Nullable String projectId, String connectionId
) throws DBWebException {
DBPDataSourceContainer dataSource = null;
if (!CommonUtils.isEmpty(connectionId)) {
WebSessionProjectImpl project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
dataSource = project.getDataSourceRegistry().getDataSource(connectionId);
if (dataSource == null &&
(webSession.hasPermission(DBWConstants.PERMISSION_ADMIN) || webSession.getApplication().isConfigurationMode())) {
dataSource = webSession.getProjectById(projectId).getDataSourceRegistry().getDataSource(connectionId);
if (dataSource == null && (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN) || application.isConfigurationMode())) {
// If called for new connection in admin mode then this connection may absent in session registry yet
project = webSession.getGlobalProject();
if (project != null) {
dataSource = project.getDataSourceRegistry().getDataSource(connectionId);
}
dataSource = getGlobalDataSourceRegistry().getDataSource(connectionId);
}
}
return dataSource;
@@ -166,28 +153,4 @@ public class WebDataSourceUtils {
}
return false;
}
/**
* The method that seeks for web connection in session cache by connection id.
* Mostly used when project id is not defined.
*/
@NotNull
public static WebConnectionInfo getWebConnectionInfo(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId
) throws DBWebException {
if (projectId == null) {
webSession.addWarningMessage("Project id is not defined in request. Try to find it from connection cache");
// try to find connection in all accessible projects
Optional<WebConnectionInfo> optional = webSession.getAccessibleProjects().stream()
.flatMap(p -> p.getConnections().stream()) // get connection cache from web projects
.filter(e -> e.getId().contains(connectionId))
.findFirst();
if (optional.isPresent()) {
return optional.get();
}
}
return webSession.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
}
}
@@ -17,15 +17,62 @@
package io.cloudbeaver.utils;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import java.io.File;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class WebTestUtils {
public static final String GQL_TEMPLATE_AUTH_LOGIN = "authLogin.json";
public static String readScriptTemplate(String templateName, Path scriptsPath) throws Exception {
Path templatePath = new File(String.valueOf(scriptsPath), templateName).toPath();
return Files.readString(templatePath);
}
public static Map<String, Object> doPost(String apiUrl, String input, HttpClient client) throws Exception {
return doPostWithHeaders(apiUrl, input, client, List.of());
}
public static Map<String, Object> doPostWithHeaders(
String apiUrl,
String input,
HttpClient client,
List<String> headers
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(input))
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
.header("Content-Type", "application/json");
if (!headers.isEmpty()) {
requestBuilder.headers(headers.toArray(String[]::new));
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
return new GsonBuilder().create().fromJson(
response.body(),
new TypeToken<Map<String, Object>>() {
}.getType()
);
}
public static boolean getServerStatus(HttpClient client, String apiUrl) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
@@ -39,4 +86,14 @@ public class WebTestUtils {
return false;
}
}
public static Map<String, Object> authenticateUser(HttpClient client, Path scriptsPath, String apiUrl) throws Exception {
String input = WebTestUtils.readScriptTemplate(GQL_TEMPLATE_AUTH_LOGIN, scriptsPath);
Map<String, Object> map = WebTestUtils.doPost(apiUrl, input, client);
Map<String, Object> data = JSONUtils.getObjectOrNull(map, "data");
if (data != null) {
return JSONUtils.getObjectOrNull(data, "authInfo");
}
return Collections.emptyMap();
}
}
@@ -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: 24.2.2.qualifier
Bundle-Release-Date: 20241007
Bundle-Version: 24.0.2.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>24.2.2-SNAPSHOT</version>
<version>24.0.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.107.qualifier
Bundle-Release-Date: 20241007
Bundle-Version: 1.0.95.qualifier
Bundle-Release-Date: 20240408
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.107-SNAPSHOT</version>
<version>1.0.95-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,13 +3,14 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 24.2.2.qualifier
Bundle-Release-Date: 20241007
Bundle-Version: 24.0.2.qualifier
Bundle-Release-Date: 20240408
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
Bundle-ClassPath: .
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
org.eclipse.core.resources;visibility:=reexport,
org.apache.commons.jexl,
org.jkiss.utils;visibility:=reexport,
org.jkiss.dbeaver.model.sql;visibility:=reexport,
@@ -25,14 +26,9 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
io.cloudbeaver.model.user,
io.cloudbeaver.registry,
io.cloudbeaver.server.events,
io.cloudbeaver.server,
io.cloudbeaver.server.actions,
io.cloudbeaver.server.jetty,
io.cloudbeaver.server.graphql,
io.cloudbeaver.server.jobs,
io.cloudbeaver.server.servlets,
io.cloudbeaver.server.websockets,
io.cloudbeaver.service,
io.cloudbeaver.service.navigator,
io.cloudbeaver.service.session,
@@ -70,7 +70,7 @@
<eventHandler class="io.cloudbeaver.server.events.WSDeleteTempFileHandler">
<topic id="cb_delete_temp_folder"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSUserEventHandler">
<eventHandler class="io.cloudbeaver.server.events.WSUserDeletedEventHandler">
<topic id="cb_user"/>
</eventHandler>
</extension>

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