Compare commits

..

8 Commits

Author SHA1 Message Date
Dario Ghunney Ware
99886f9a70 clean up 2025-05-22 08:24:23 +01:00
Dario Ghunney Ware
f8b2b0e6d7 activeSecurity > disableSecurity 2025-05-22 08:24:23 +01:00
Dario Ghunney Ware
ae2e16867f updating paths (DOCKER_SECURITY_ENABLE > ADDITIONAL_FEATURES) 2025-05-22 08:23:56 +01:00
Dario Ghunney Ware
26b532805f renamed module: enterprise > proprietary 2025-05-22 08:23:32 +01:00
Dario Ghunney Ware
7d4baf22dc renaming module 2025-05-22 08:23:09 +01:00
Dario Ghunney Ware
2f221d4235 adding more config to common module 2025-05-22 08:23:08 +01:00
Dario Ghunney Ware
25f1c1cbe8 adding new common module 2025-05-22 08:23:08 +01:00
Dario Ghunney Ware
4121ac2132 adding conditionals to db and sessions classes 2025-05-22 08:22:28 +01:00
308 changed files with 4372 additions and 3932 deletions

View File

@ -102,8 +102,8 @@
"java.eclipse.downloadSources": true, "java.eclipse.downloadSources": true,
"java.import.gradle.wrapper.enabled": true, "java.import.gradle.wrapper.enabled": true,
"spring.initializr.defaultLanguage": "Java", "spring.initializr.defaultLanguage": "Java",
"spring.initializr.defaultGroupId": "stirling.software.SPDF", "spring.initializr.defaultGroupId": "stirling.software.spdf",
"spring.initializr.defaultArtifactId": "SPDF" "spring.initializr.defaultArtifactId": "spdf"
}, },
"extensions": [ "extensions": [
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality "elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality

View File

@ -1,33 +0,0 @@
name: 'Setup GitHub App Bot'
description: 'Generates a GitHub App Token and configures Git for a bot'
inputs:
app-id:
description: 'GitHub App ID'
required: True
private-key:
description: 'GitHub App Private Key'
required: True
outputs:
token:
description: 'Generated GitHub App Token'
value: ${{ steps.generate-token.outputs.token }}
committer:
description: 'Committer string for Git'
value: "${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
app-slug:
description: 'GitHub App slug'
value: ${{ steps.generate-token.outputs.app-slug }}
runs:
using: 'composite'
steps:
- name: Generate a GitHub App Token
id: generate-token
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
- name: Configure Git
run: |
git config --global user.name "${{ steps.generate-token.outputs.app-slug }}[bot]"
git config --global user.email "${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
shell: bash

View File

@ -8,8 +8,8 @@ Front End:
- changed-files: - changed-files:
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/static/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/resources/static/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/**' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/controller/web/**'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/UI/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/UI/**/*'
Java: Java:
- changed-files: - changed-files:
@ -19,8 +19,8 @@ Java:
Back End: Back End:
- changed-files: - changed-files:
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/config/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/controller/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/settings.yml.template' - any-glob-to-any-file: 'stirling-pdf/src/main/resources/settings.yml.template'
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/application.properties' - any-glob-to-any-file: 'stirling-pdf/src/main/resources/application.properties'
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/banner.txt' - any-glob-to-any-file: 'stirling-pdf/src/main/resources/banner.txt'
@ -36,10 +36,10 @@ Security:
API: API:
- changed-files: - changed-files:
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/config/OpenApiConfig.java'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/controller/web/MetricsController.java'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/controller/api/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/model/api/**/*' - any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/spdf/model/api/**/*'
- any-glob-to-any-file: 'scripts/png_to_webp.py' - any-glob-to-any-file: 'scripts/png_to_webp.py'
- any-glob-to-any-file: 'split_photos.py' - any-glob-to-any-file: 'split_photos.py'
- any-glob-to-any-file: '.github/workflows/swagger.yml' - any-glob-to-any-file: '.github/workflows/swagger.yml'

View File

@ -156,9 +156,9 @@ jobs:
- name: Run Gradle Command - name: Run Gradle Command
run: | run: |
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
export ADDITIONAL_FEATURES_OFF=false export WITHOUT_ENHANCED_FEATURES=false
else else
export ADDITIONAL_FEATURES_OFF=true export WITHOUT_ENHANCED_FEATURES=true
fi fi
./gradlew clean build ./gradlew clean build
env: env:
@ -180,7 +180,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image - name: Build and push PR-specific image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@ -200,11 +200,11 @@ jobs:
run: | run: |
# Set security settings based on flags # Set security settings based on flags
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
DOCKER_SECURITY="true" WITHOUT_ENHANCED_FEATURES="false"
LOGIN_SECURITY="true" LOGIN_SECURITY="true"
SECURITY_STATUS="🔒 Security Enabled" SECURITY_STATUS="🔒 Security Enabled"
else else
DOCKER_SECURITY="false" WITHOUT_ENHANCED_FEATURES="true"
LOGIN_SECURITY="false" LOGIN_SECURITY="false"
SECURITY_STATUS="Security Disabled" SECURITY_STATUS="Security Disabled"
fi fi
@ -223,7 +223,7 @@ jobs:
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw - /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw - /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "${DOCKER_SECURITY}" WITHOUT_ENHANCED_FEATURES: "${WITHOUT_ENHANCED_FEATURES}"
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}" SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
SYSTEM_DEFAULTLOCALE: en-GB SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}" UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"

View File

@ -40,12 +40,12 @@ jobs:
- name: Build with Gradle and no spring security - name: Build with Gradle and no spring security
run: ./gradlew clean build run: ./gradlew clean build
env: env:
ADDITIONAL_FEATURES_OFF: true WITHOUT_ENHANCED_FEATURES: true
- name: Build with Gradle and with spring security - name: Build with Gradle and with spring security
run: ./gradlew clean build run: ./gradlew clean build
env: env:
ADDITIONAL_FEATURES_OFF: false WITHOUT_ENHANCED_FEATURES: false
- name: Upload Test Reports - name: Upload Test Reports
if: always() if: always()
@ -56,9 +56,6 @@ jobs:
build/reports/tests/ build/reports/tests/
build/test-results/ build/test-results/
build/reports/problems/ build/reports/problems/
/common/build/reports/tests/
/common/build/test-results/
/common/build/reports/problems/
retention-days: 3 retention-days: 3
check-licence: check-licence:

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
issues: write # Allow posting comments on issues/PRs issues: write # Allow posting comments on issues/PRs
pull-requests: write # Allow writing to pull requests pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
@ -25,18 +25,15 @@ jobs:
- name: Checkout main branch first - name: Checkout main branch first
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup GitHub App Bot - name: Set up Python
id: setup-bot uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: ./.github/actions/setup-bot
with: with:
app-id: ${{ secrets.GH_APP_ID }} python-version: "3.12"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get PR data - name: Get PR data
id: get-pr-data id: get-pr-data
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with: with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: | script: |
const prNumber = context.payload.pull_request.number; const prNumber = context.payload.pull_request.number;
const repoOwner = context.payload.repository.owner.login; const repoOwner = context.payload.repository.owner.login;
@ -57,7 +54,7 @@ jobs:
- name: Fetch PR changed files - name: Fetch PR changed files
id: fetch-pr-changes id: fetch-pr-changes
env: env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
echo "Fetching PR changed files..." echo "Fetching PR changed files..."
echo "Getting list of changed files from PR..." echo "Getting list of changed files from PR..."
@ -67,7 +64,6 @@ jobs:
id: determine-file id: determine-file
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with: with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: | script: |
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
@ -208,7 +204,6 @@ jobs:
if: env.SCRIPT_OUTPUT != '' if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with: with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: | script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env; const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/'); const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
@ -224,7 +219,7 @@ jobs:
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary")); const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
// Only update or create comments by the action user // Only update or create comments by the action user
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]"; const expectedActor = "github-actions[bot]";
if (comment && comment.user.login === expectedActor) { if (comment && comment.user.login === expectedActor) {
// Update existing comment // Update existing comment

View File

@ -16,50 +16,52 @@ jobs:
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
repository-projects: write # Required for enabling automerge
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with: with:
egress-policy: audit egress-policy: audit
- name: Check out code - name: Generate GitHub App Token
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 id: generate-token
with: uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
fetch-depth: 0
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
java-version: "17" java-version: "17"
distribution: "adopt" distribution: "adopt"
- name: Setup Gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
- name: Check licenses for compatibility - name: check the licenses for compatibility
run: ./gradlew clean checkLicense run: ./gradlew clean checkLicense
- name: Upload artifact on failure - name: FAILED - check the licenses for compatibility
if: failure() if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: dependencies-without-allowed-license.json name: dependencies-without-allowed-license.json
path: build/reports/dependency-license/dependencies-without-allowed-license.json path: |
build/reports/dependency-license/dependencies-without-allowed-license.json
retention-days: 3 retention-days: 3
- name: Move and rename license file - name: Move and Rename License File
run: | run: |
mv build/reports/dependency-license/index.json stirling-pdf/src/main/resources/static/3rdPartyLicenses.json mv build/reports/dependency-license/index.json stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
- name: Commit changes - name: Set up git config
run: |
git config --global user.name "stirlingbot[bot]"
git config --global user.email "1113334+stirlingbot[bot]@users.noreply.github.com"
- name: Run git add
run: | run: |
git add stirling-pdf/src/main/resources/static/3rdPartyLicenses.json git add stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
@ -69,15 +71,15 @@ jobs:
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
token: ${{ steps.setup-bot.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
commit-message: "Update 3rd Party Licenses" commit-message: "Update 3rd Party Licenses"
committer: ${{ steps.setup-bot.outputs.committer }} committer: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
author: ${{ steps.setup-bot.outputs.committer }} author: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
signoff: true signoff: true
branch: update-3rd-party-licenses branch: update-3rd-party-licenses
title: "Update 3rd Party Licenses" title: "Update 3rd Party Licenses"
body: | body: |
Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot] Auto-generated by StirlingBot
labels: licenses,github-actions labels: licenses,github-actions
draft: false draft: false
delete-branch: true delete-branch: true
@ -87,4 +89,4 @@ jobs:
if: steps.cpr.outputs.pull-request-operation == 'created' if: steps.cpr.outputs.pull-request-operation == 'created'
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}" run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
env: env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }} GH_TOKEN: ${{ steps.generate-token.outputs.token }}

View File

@ -50,10 +50,10 @@ jobs:
matrix: matrix:
disable_security: [true, false] disable_security: [true, false]
include: include:
- disable_security: true
file_suffix: ""
- disable_security: false - disable_security: false
file_suffix: "-with-login" file_suffix: "-with-login"
- disable_security: true
file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
@ -72,10 +72,10 @@ jobs:
with: with:
gradle-version: 8.14 gradle-version: 8.14
- name: Generate jar (Without Security=${{ matrix.disable_security }}) - name: Generate jar (With Security=${{ matrix.disable_security }})
run: ./gradlew clean createExe run: ./gradlew clean createExe
env: env:
ADDITIONAL_FEATURES_OFF: ${{ matrix.disable_security }} WITHOUT_ENHANCED_FEATURES: ${{ matrix.disable_security }}
STIRLING_PDF_DESKTOP_UI: false STIRLING_PDF_DESKTOP_UI: false
- name: Rename binaries - name: Rename binaries
@ -100,10 +100,10 @@ jobs:
matrix: matrix:
disable_security: [true, false] disable_security: [true, false]
include: include:
- disable_security: true
file_suffix: ""
- disable_security: false - disable_security: false
file_suffix: "with-login-" file_suffix: "with-login-"
- disable_security: true
file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
@ -171,7 +171,7 @@ jobs:
- name: Build Installer - name: Build Installer
run: ./gradlew build jpackage -x test --info run: ./gradlew build jpackage -x test --info
env: env:
ADDITIONAL_FEATURES_OFF: true WITHOUT_ENHANCED_FEATURES: true
STIRLING_PDF_DESKTOP_UI: true STIRLING_PDF_DESKTOP_UI: true
BROWSER_OPEN: true BROWSER_OPEN: true

View File

@ -20,49 +20,58 @@ jobs:
with: with:
egress-policy: audit egress-policy: audit
- name: Checkout repository - name: Generate GitHub App Token
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 id: generate-token
with: uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
fetch-depth: 0
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.generate-token.outputs.app-slug }}[bot]" --jq .id)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- id: committer
run: |
echo "string=${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: with:
python-version: 3.12 python-version: 3.12
cache: 'pip' # caching pip dependencies cache: 'pip' # caching pip dependencies
- name: Run Pre-Commit Hooks - name: Run Pre-Commit Hooks
run: | run: |
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
- run: pre-commit run --all-files -c .pre-commit-config.yaml - run: pre-commit run --all-files -c .pre-commit-config.yaml
continue-on-error: true continue-on-error: true
- name: Set up git config
run: |
git config --global user.name ${{ steps.generate-token.outputs.app-slug }}[bot]
git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
- name: git add - name: git add
run: | run: |
git add . git add .
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request - name: Create Pull Request
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
token: ${{ steps.setup-bot.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
commit-message: ":file_folder: pre-commit" commit-message: ":file_folder: pre-commit"
committer: ${{ steps.setup-bot.outputs.committer }} committer: ${{ steps.committer.outputs.string }}
author: ${{ steps.setup-bot.outputs.committer }} author: ${{ steps.committer.outputs.string }}
signoff: true signoff: true
branch: pre-commit branch: pre-commit
title: "🤖 format everything with pre-commit by ${{ steps.setup-bot.outputs.app-slug }}" title: "🤖 format everything with pre-commit by <${{ steps.generate-token.outputs.app-slug }}>"
body: | body: |
Auto-generated by [create-pull-request][1] with **${{ steps.setup-bot.outputs.app-slug }}** Auto-generated by [create-pull-request][1] with **${{ steps.generate-token.outputs.app-slug }}**
[1]: https://github.com/peter-evans/create-pull-request [1]: https://github.com/peter-evans/create-pull-request
draft: false draft: false

View File

@ -37,7 +37,7 @@ jobs:
- name: Run Gradle Command - name: Run Gradle Command
run: ./gradlew clean build run: ./gradlew clean build
env: env:
ADDITIONAL_FEATURES_OFF: true WITHOUT_ENHANCED_FEATURES: true
STIRLING_PDF_DESKTOP_UI: false STIRLING_PDF_DESKTOP_UI: false
- name: Install cosign - name: Install cosign
@ -90,7 +90,7 @@ jobs:
- name: Build and push main Dockerfile - name: Build and push main Dockerfile
id: build-push-regular id: build-push-regular
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
context: . context: .
@ -135,7 +135,7 @@ jobs:
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
id: build-push-lite id: build-push-lite
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
context: . context: .
@ -166,7 +166,7 @@ jobs:
- name: Build and push main Dockerfile fat - name: Build and push main Dockerfile fat
id: build-push-fat id: build-push-fat
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}

View File

@ -13,17 +13,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
enable_security: [true, false]
disable_security: [true, false] disable_security: [true, false]
include: include:
- enable_security: true
file_suffix: "-with-login"
- enable_security: false
file_suffix: ""
- disable_security: true
file_suffix: ""
- disable_security: false - disable_security: false
file_suffix: "-with-login" file_suffix: "-with-login"
- disable_security: true
file_suffix: ""
outputs: outputs:
version: ${{ steps.versionNumber.outputs.versionNumber }} version: ${{ steps.versionNumber.outputs.versionNumber }}
steps: steps:
@ -44,10 +39,10 @@ jobs:
with: with:
gradle-version: 8.14 gradle-version: 8.14
- name: Generate jar (With Security=${{ matrix.enable_security }}) - name: Generate jar (With Security=${{ matrix.disable_security }})
run: ./gradlew clean createExe run: ./gradlew clean createExe
env: env:
ADDITIONAL_FEATURES_OFF: ${{ matrix.disable_security }} WITHOUT_ENHANCED_FEATURES: ${{ matrix.disable_security }}
STIRLING_PDF_DESKTOP_UI: false STIRLING_PDF_DESKTOP_UI: false
- name: Get version number - name: Get version number
@ -80,11 +75,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
enable_security: [true, false] disable_security: [true, false]
include: include:
- enable_security: true - disable_security: false
file_suffix: "-with-login" file_suffix: "-with-login"
- enable_security: false - disable_security: true
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
@ -158,11 +153,11 @@ jobs:
contents: write contents: write
strategy: strategy:
matrix: matrix:
enable_security: [true, false] disable_security: [true, false]
include: include:
- enable_security: true - disable_security: false
file_suffix: "-with-login" file_suffix: "-with-login"
- enable_security: false - disable_security: true
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner

View File

@ -44,7 +44,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif

View File

@ -33,7 +33,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
ADDITIONAL_FEATURES_OFF: false WITHOUT_ENHANCED_FEATURES: false
STIRLING_PDF_DESKTOP_UI: true STIRLING_PDF_DESKTOP_UI: true
run: | run: |
./gradlew clean build sonar \ ./gradlew clean build sonar \

View File

@ -16,7 +16,44 @@ permissions:
contents: read contents: read
jobs: jobs:
read_bot_entries:
runs-on: ubuntu-latest
outputs:
userName: ${{ steps.get-user-id.outputs.user_name }}
userEmail: ${{ steps.get-user-id.outputs.user_email }}
committer: ${{ steps.committer.outputs.committer }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: |
USER_NAME="${{ steps.generate-token.outputs.app-slug }}[bot]"
USER_ID=$(gh api "/users/$USER_NAME" --jq .id)
USER_EMAIL="$USER_ID+$USER_NAME@users.noreply.github.com"
echo "user_name=$USER_NAME" >> "$GITHUB_OUTPUT"
echo "user_email=$USER_EMAIL" >> "$GITHUB_OUTPUT"
echo "user-id=$USER_ID" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- id: committer
run: |
COMMITTER="${{ steps.get-user-id.outputs.user_name }} <${{ steps.get-user-id.outputs.user_email }}>"
echo "committer=$COMMITTER" >> "$GITHUB_OUTPUT"
sync-files: sync-files:
needs: ["read_bot_entries"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
@ -24,29 +61,34 @@ jobs:
with: with:
egress-policy: audit egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Generate GitHub App Token
id: generate-token
- name: Setup GitHub App Bot uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
id: setup-bot
uses: ./.github/actions/setup-bot
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ vars.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python - name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: with:
python-version: "3.12" python-version: "3.12"
cache: "pip" # caching pip dependencies cache: 'pip' # caching pip dependencies
- name: Sync translation property files - name: Sync translation property files
run: | run: |
python .github/scripts/check_language_properties.py --reference-file "stirling-pdf/src/main/resources/messages_en_GB.properties" --branch main python .github/scripts/check_language_properties.py --reference-file "stirling-pdf/src/main/resources/messages_en_GB.properties" --branch main
- name: Commit translation files - name: Set up git config
run: |
git config --global user.name ${{ needs.read_bot_entries.outputs.userName }}
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
- name: Run git add
run: | run: |
git add stirling-pdf/src/main/resources/messages_*.properties git add stirling-pdf/src/main/resources/messages_*.properties
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected" git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
- name: Install dependencies - name: Install dependencies
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
@ -58,16 +100,15 @@ jobs:
- name: Run git add - name: Run git add
run: | run: |
git add README.md git add README.md
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "No changes detected" git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
- name: Create Pull Request - name: Create Pull Request
if: always()
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
token: ${{ steps.setup-bot.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
commit-message: Update files commit-message: Update files
committer: ${{ steps.setup-bot.outputs.committer }} committer: ${{ needs.read_bot_entries.outputs.committer }}
author: ${{ steps.setup-bot.outputs.committer }} author: ${{ needs.read_bot_entries.outputs.committer }}
signoff: true signoff: true
branch: sync_readme branch: sync_readme
title: ":globe_with_meridians: Sync Translations + Update README Progress Table" title: ":globe_with_meridians: Sync Translations + Update README Progress Table"

View File

@ -28,7 +28,7 @@ jobs:
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew clean build run: ./gradlew clean build
env: env:
ADDITIONAL_FEATURES_OFF: true WITHOUT_ENHANCED_FEATURES: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
@ -46,7 +46,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push test image - name: Build and push test image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@ -76,7 +76,7 @@ jobs:
- /stirling/test-${{ github.sha }}/config:/configs:rw - /stirling/test-${{ github.sha }}/config:/configs:rw
- /stirling/test-${{ github.sha }}/logs:/logs:rw - /stirling/test-${{ github.sha }}/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "true" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF Test" UI_APPNAME: "Stirling-PDF Test"

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11 rev: v0.11.6
hooks: hooks:
- id: ruff - id: ruff
args: args:
@ -22,7 +22,7 @@ repos:
files: \.(html|css|js|py|md)$ files: \.(html|css|js|py|md)$
exclude: (.vscode|.devcontainer|stirling-pdf/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js) exclude: (.vscode|.devcontainer|stirling-pdf/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.26.0 rev: v8.24.3
hooks: hooks:
- id: gitleaks - id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks

12
.vscode/settings.json vendored
View File

@ -10,7 +10,7 @@
"java.configuration.updateBuildConfiguration": "interactive", "java.configuration.updateBuildConfiguration": "interactive",
"java.format.enabled": true, "java.format.enabled": true,
"java.format.settings.profile": "GoogleStyle", "java.format.settings.profile": "GoogleStyle",
"java.format.settings.google.version": "1.27.0", "java.format.settings.google.version": "1.26.0",
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting", "java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
// (DE) Aktiviert Kommentare im Java-Format. // (DE) Aktiviert Kommentare im Java-Format.
// (EN) Enables comments in Java formatting. // (EN) Enables comments in Java formatting.
@ -49,11 +49,7 @@
".venv*/", ".venv*/",
".vscode/", ".vscode/",
"bin/", "bin/",
"common/bin/",
"proprietary/bin/",
"build/", "build/",
"common/build/",
"proprietary/build/",
"configs/", "configs/",
"customFiles/", "customFiles/",
"docs/", "docs/",
@ -67,8 +63,6 @@
".git-blame-ignore-revs", ".git-blame-ignore-revs",
".gitattributes", ".gitattributes",
".gitignore", ".gitignore",
"common/.gitignore",
"proprietary/.gitignore",
".pre-commit-config.yaml", ".pre-commit-config.yaml",
], ],
// Enables signature help in Java. // Enables signature help in Java.
@ -84,6 +78,6 @@
// Enables import of the Gradle wrapper. // Enables import of the Gradle wrapper.
"java.import.gradle.wrapper.enabled": true, "java.import.gradle.wrapper.enabled": true,
"spring.initializr.defaultLanguage": "Java", "spring.initializr.defaultLanguage": "Java",
"spring.initializr.defaultGroupId": "stirling.software.SPDF", "spring.initializr.defaultGroupId": "stirling.software.spdf",
"spring.initializr.defaultArtifactId": "SPDF", "spring.initializr.defaultArtifactId": "spdf",
} }

View File

@ -55,7 +55,7 @@ Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, do
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE. Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
5. Add environment variable 5. Add environment variable
For local testing, you should generally be testing the full 'Security' version of Stirling-PDF. To do this, you must add the environment flag ADDITIONAL_FEATURES_OFF=false to your system and/or IDE build/run step. For local testing, you should generally be testing the full 'Security' version of Stirling-PDF. Security is enabled by default. To disable it, you must add the environment flag WITHOUT_ENHANCED_FEATURES=true to your system and/or IDE build/run step.
## 4. Project Structure ## 4. Project Structure
@ -76,7 +76,7 @@ Stirling-PDF/
│ │ ├── java/ │ │ ├── java/
│ │ │ └── stirling/ │ │ │ └── stirling/
│ │ │ └── software/ │ │ │ └── software/
│ │ │ └── SPDF/ │ │ │ └── spdf/
│ │ │ ├── config/ │ │ │ ├── config/
│ │ │ ├── controller/ │ │ │ ├── controller/
│ │ │ ├── model/ │ │ │ ├── model/
@ -93,7 +93,7 @@ Stirling-PDF/
│ └── java/ │ └── java/
│ └── stirling/ │ └── stirling/
│ └── software/ │ └── software/
│ └── SPDF/ │ └── spdf/
├── build.gradle # Gradle build configuration ├── build.gradle # Gradle build configuration
├── Dockerfile # Main Dockerfile ├── Dockerfile # Main Dockerfile
├── Dockerfile.ultra-lite # Dockerfile for ultra-lite version ├── Dockerfile.ultra-lite # Dockerfile for ultra-lite version
@ -141,7 +141,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "true"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002
@ -170,7 +170,7 @@ Stirling-PDF uses different Docker images for various configurations. The build
1. Set the security environment variable: 1. Set the security environment variable:
```bash ```bash
export ADDITIONAL_FEATURES_OFF=true # or false for security-enabled builds export WITHOUT_ENHANCED_FEATURES=false # or true for security-enabled builds
``` ```
2. Build the project with Gradle: 2. Build the project with Gradle:
@ -196,7 +196,7 @@ Stirling-PDF uses different Docker images for various configurations. The build
For the fat version (with security enabled): For the fat version (with security enabled):
```bash ```bash
export ADDITIONAL_FEATURES_OFF=false export WITHOUT_ENHANCED_FEATURES=true
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
``` ```
@ -384,12 +384,12 @@ This would generate n entries of tr for each person in exampleData
### Adding a New Feature to the Backend (API) ### Adding a New Feature to the Backend (API)
1. **Create a New Controller:** 1. **Create a New Controller:**
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/api` directory. - Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/spdf/controller/api` directory.
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint. - Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`. - Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
```java ```java
package stirling.software.SPDF.controller.api; package stirling.software.spdf.controller.api;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -411,11 +411,11 @@ This would generate n entries of tr for each person in exampleData
``` ```
2. **Define the Service Layer:** (Not required but often useful) 2. **Define the Service Layer:** (Not required but often useful)
- Create a new service class in the `stirling-pdf/src/main/java/stirling/software/SPDF/service` directory. - Create a new service class in the `stirling-pdf/src/main/java/stirling/software/spdf/service` directory.
- Implement the business logic for the new feature. - Implement the business logic for the new feature.
```java ```java
package stirling.software.SPDF.service; package stirling.software.spdf.service;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -434,13 +434,13 @@ This would generate n entries of tr for each person in exampleData
- Autowire the service class in the controller and use it to handle the API request. - Autowire the service class in the controller and use it to handle the API request.
```java ```java
package stirling.software.SPDF.controller.api; package stirling.software.spdf.controller.api;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import stirling.software.SPDF.service.NewFeatureService; import stirling.software.spdf.service.NewFeatureService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -507,18 +507,18 @@ This would generate n entries of tr for each person in exampleData
``` ```
2. **Create a New Controller for the UI:** 2. **Create a New Controller for the UI:**
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/ui` directory. - Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/spdf/controller/ui` directory.
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint. - Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
```java ```java
package stirling.software.SPDF.controller.ui; package stirling.software.spdf.controller.ui;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import stirling.software.SPDF.service.NewFeatureService; import stirling.software.spdf.service.NewFeatureService;
@Controller @Controller
@RequestMapping("/new-feature") @RequestMapping("/new-feature")

View File

@ -1,5 +1,5 @@
# Main stage # Main stage
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts
@ -23,7 +23,8 @@ LABEL org.opencontainers.image.version="${VERSION_TAG}"
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark" LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
# Set Environment Variables # Set Environment Variables
ENV ADDITIONAL_FEATURES_OFF=true \ # todo: keep security off?
ENV WITHOUT_ENHANCED_FEATURES=true \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
JAVA_CUSTOM_OPTS="" \ JAVA_CUSTOM_OPTS="" \
@ -73,7 +74,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
py3-pillow@testing \ py3-pillow@testing \
py3-pdf2image@testing && \ py3-pdf2image@testing && \
python3 -m venv /opt/venv && \ python3 -m venv /opt/venv && \
/opt/venv/bin/pip install --upgrade pip setuptools && \ /opt/venv/bin/pip install --upgrade pip && \
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \

View File

@ -32,7 +32,6 @@ ENV SETUPTOOLS_USE_DISTUTILS=local
# Installation der benötigten Python-Pakete # Installation der benötigten Python-Pakete
RUN python3 -m venv --system-site-packages /opt/venv \ RUN python3 -m venv --system-site-packages /opt/venv \
&& . /opt/venv/bin/activate \ && . /opt/venv/bin/activate \
&& pip install --upgrade pip setuptools \
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit && pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind # Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind

View File

@ -5,8 +5,6 @@ COPY build.gradle .
COPY settings.gradle . COPY settings.gradle .
COPY gradlew . COPY gradlew .
COPY gradle gradle/ COPY gradle gradle/
COPY common/build.gradle common/.
COPY proprietary/build.gradle proprietary/.
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
# Set the working directory # Set the working directory
@ -15,13 +13,13 @@ WORKDIR /app
# Copy the entire project to the working directory # Copy the entire project to the working directory
COPY . . COPY . .
# Build the application with ADDITIONAL_FEATURES_OFF=false # Build the application with WITHOUT_ENHANCED_FEATURES=false
RUN ADDITIONAL_FEATURES_OFF=false \ RUN WITHOUT_ENHANCED_FEATURES=false \
STIRLING_PDF_DESKTOP_UI=false \ STIRLING_PDF_DESKTOP_UI=false \
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
# Main stage # Main stage
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts
@ -32,7 +30,7 @@ COPY --from=build /app/build/libs/*.jar app.jar
ARG VERSION_TAG ARG VERSION_TAG
# Set Environment Variables # Set Environment Variables
ENV ADDITIONAL_FEATURES_OFF=true \ ENV WITHOUT_ENHANCED_FEATURES=false \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
JAVA_CUSTOM_OPTS="" \ JAVA_CUSTOM_OPTS="" \
@ -85,7 +83,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
py3-pillow@testing \ py3-pillow@testing \
py3-pdf2image@testing && \ py3-pdf2image@testing && \
python3 -m venv /opt/venv && \ python3 -m venv /opt/venv && \
/opt/venv/bin/pip install --upgrade pip setuptools && \ /opt/venv/bin/pip install --upgrade pip && \
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \

View File

@ -1,10 +1,10 @@
# use alpine # use alpine
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
ARG VERSION_TAG ARG VERSION_TAG
# Set Environment Variables # Set Environment Variables
ENV ADDITIONAL_FEATURES_OFF=true \ ENV WITHOUT_ENHANCED_FEATURES=true \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \

22
LICENSE
View File

@ -1,27 +1,11 @@
MIT License MIT License
Copyright (c) 2025 Stirling PDF Inc. Copyright (c) 2024 Stirling Tools
Portions of this software are licensed as follows: Portions of this software are licensed as follows:
* All content that resides under the "proprietary/" directory of this repository, * All content that resides under the "proprietary/" directory of this repository, if that directory exists, is licensed under the license defined in "proprietary/LICENSE".
if that directory exists, is licensed under the license defined in "proprietary/LICENSE-proprietary". * Content outside of the above mentioned directories or restrictions above is available under the MIT License as defined below.
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
Portions of this software are licensed as follows:
* All content that resides under the "proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "proprietary/LICENSE-proprietary".
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
Portions of this software are licensed as follows:
* All content that resides under the "proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "proprietary/LICENSE-proprietary".
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![73%](https://geps.dev/progress/73) | | Arabic (العربية) (ar_AR) | ![83%](https://geps.dev/progress/83) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![74%](https://geps.dev/progress/74) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![82%](https://geps.dev/progress/82) |
| Basque (Euskara) (eu_ES) | ![43%](https://geps.dev/progress/43) | | Basque (Euskara) (eu_ES) | ![48%](https://geps.dev/progress/48) |
| Bulgarian (Български) (bg_BG) | ![81%](https://geps.dev/progress/81) | | Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) |
| Catalan (Català) (ca_CA) | ![81%](https://geps.dev/progress/81) | | Catalan (Català) (ca_CA) | ![89%](https://geps.dev/progress/89) |
| Croatian (Hrvatski) (hr_HR) | ![72%](https://geps.dev/progress/72) | | Croatian (Hrvatski) (hr_HR) | ![81%](https://geps.dev/progress/81) |
| Czech (Česky) (cs_CZ) | ![83%](https://geps.dev/progress/83) | | Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) |
| Danish (Dansk) (da_DK) | ![73%](https://geps.dev/progress/73) | | Danish (Dansk) (da_DK) | ![80%](https://geps.dev/progress/80) |
| Dutch (Nederlands) (nl_NL) | ![71%](https://geps.dev/progress/71) | | Dutch (Nederlands) (nl_NL) | ![79%](https://geps.dev/progress/79) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![82%](https://geps.dev/progress/82) | | French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![89%](https://geps.dev/progress/89) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) | | Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) |
| Hindi (हिंदी) (hi_IN) | ![80%](https://geps.dev/progress/80) | | Hindi (हिंदी) (hi_IN) | ![91%](https://geps.dev/progress/91) |
| Hungarian (Magyar) (hu_HU) | ![87%](https://geps.dev/progress/87) | | Hungarian (Magyar) (hu_HU) | ![99%](https://geps.dev/progress/99) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![74%](https://geps.dev/progress/74) | | Indonesian (Bahasa Indonesia) (id_ID) | ![80%](https://geps.dev/progress/80) |
| Irish (Gaeilge) (ga_IE) | ![81%](https://geps.dev/progress/81) | | Irish (Gaeilge) (ga_IE) | ![91%](https://geps.dev/progress/91) |
| Italian (Italiano) (it_IT) | ![95%](https://geps.dev/progress/95) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![82%](https://geps.dev/progress/82) | | Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![80%](https://geps.dev/progress/80) | | Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) |
| Norwegian (Norsk) (no_NB) | ![78%](https://geps.dev/progress/78) | | Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) |
| Persian (فارسی) (fa_IR) | ![76%](https://geps.dev/progress/76) | | Persian (فارسی) (fa_IR) | ![87%](https://geps.dev/progress/87) |
| Polish (Polski) (pl_PL) | ![86%](https://geps.dev/progress/86) | | Polish (Polski) (pl_PL) | ![95%](https://geps.dev/progress/95) |
| Portuguese (Português) (pt_PT) | ![82%](https://geps.dev/progress/82) | | Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![87%](https://geps.dev/progress/87) | | Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) |
| Romanian (Română) (ro_RO) | ![68%](https://geps.dev/progress/68) | | Romanian (Română) (ro_RO) | ![75%](https://geps.dev/progress/75) |
| Russian (Русский) (ru_RU) | ![87%](https://geps.dev/progress/87) | | Russian (Русский) (ru_RU) | ![93%](https://geps.dev/progress/93) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![52%](https://geps.dev/progress/52) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![60%](https://geps.dev/progress/60) |
| Simplified Chinese (简体中文) (zh_CN) | ![86%](https://geps.dev/progress/86) | | Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![61%](https://geps.dev/progress/61) | | Slovakian (Slovensky) (sk_SK) | ![69%](https://geps.dev/progress/69) |
| Slovenian (Slovenščina) (sl_SI) | ![85%](https://geps.dev/progress/85) | | Slovenian (Slovenščina) (sl_SI) | ![94%](https://geps.dev/progress/94) |
| Spanish (Español) (es_ES) | ![88%](https://geps.dev/progress/88) | | Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) |
| Swedish (Svenska) (sv_SE) | ![78%](https://geps.dev/progress/78) | | Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) |
| Thai (ไทย) (th_TH) | ![70%](https://geps.dev/progress/70) | | Thai (ไทย) (th_TH) | ![80%](https://geps.dev/progress/80) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![77%](https://geps.dev/progress/77) | | Tibetan (བོད་ཡིག་) (zh_BO) | ![88%](https://geps.dev/progress/88) |
| Traditional Chinese (繁體中文) (zh_TW) | ![87%](https://geps.dev/progress/87) | | Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![87%](https://geps.dev/progress/87) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![87%](https://geps.dev/progress/87) | | Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![68%](https://geps.dev/progress/68) | | Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) |
| Malayalam (മലയാളം) (ml_IN) | ![87%](https://geps.dev/progress/87) | | Malayalam (മലയാളം) (ml_ML) | ![99%](https://geps.dev/progress/99) |
## Stirling PDF Enterprise ## Stirling PDF Enterprise

View File

@ -1,15 +1,15 @@
plugins { plugins {
id "java" id "java"
id "jacoco" id 'jacoco'
id "org.springframework.boot" version "3.5.0" id "org.springframework.boot" version "3.4.5"
id "io.spring.dependency-management" version "1.1.7" id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0" id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6" id "edu.sc.seis.launch4j" version "3.0.6"
id "com.diffplug.spotless" version "7.0.4" id "com.diffplug.spotless" version "7.0.3"
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
id "org.panteleyev.jpackageplugin" version "1.6.1" id("org.panteleyev.jpackageplugin") version "1.6.1"
id "org.sonarqube" version "6.2.0.5505" id "org.sonarqube" version "6.2.0.5505"
} }
@ -19,7 +19,7 @@ import java.nio.file.Files
import java.time.Year import java.time.Year
ext { ext {
springBootVersion = "3.5.0" springBootVersion = "3.4.5"
pdfboxVersion = "3.0.5" pdfboxVersion = "3.0.5"
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.38" lombokVersion = "1.18.38"
@ -45,32 +45,32 @@ bootJar {
sourceSets { sourceSets {
main { main {
java { java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('ADDITIONAL_FEATURES_OFF') == 'true' if (System.getenv('WITHOUT_ENHANCED_FEATURES') == 'false'
|| (project.hasProperty('ADDITIONAL_FEATURES_OFF') || (project.hasProperty('WITHOUT_ENHANCED_FEATURES')
&& System.getProperty('ADDITIONAL_FEATURES_OFF') == 'true')) { && System.getProperty('WITHOUT_ENHANCED_FEATURES') == 'false')) {
exclude 'stirling/software/proprietary/security/**' exclude 'stirling/software/proprietary/security/**'
} }
if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false' if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false'
|| (project.hasProperty('STIRLING_PDF_DESKTOP_UI') || (project.hasProperty('STIRLING_PDF_DESKTOP_UI')
&& project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) { && project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) {
exclude 'stirling/software/SPDF/UI/impl/**' exclude 'stirling/software/spdf/UI/impl/**'
} }
} }
} }
test { test {
java { java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('ADDITIONAL_FEATURES_OFF') == 'true' if (System.getenv('WITHOUT_ENHANCED_FEATURES') == 'false'
|| (project.hasProperty('ADDITIONAL_FEATURES_OFF') || (project.hasProperty('WITHOUT_ENHANCED_FEATURES')
&& System.getProperty('ADDITIONAL_FEATURES_OFF') == 'true')) { && System.getProperty('WITHOUT_ENHANCED_FEATURES') == 'false')) {
exclude 'stirling/software/proprietary/security/**' exclude 'stirling/software/proprietary/security/**'
} }
if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false' if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false'
|| (project.hasProperty('STIRLING_PDF_DESKTOP_UI') || (project.hasProperty('STIRLING_PDF_DESKTOP_UI')
&& project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) { && project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) {
exclude 'stirling/software/SPDF/UI/impl/**' exclude 'stirling/software/spdf/UI/impl/**'
} }
} }
} }
@ -87,16 +87,10 @@ allprojects {
distributionType = Wrapper.DistributionType.ALL distributionType = Wrapper.DistributionType.ALL
} }
} }
configurations.all {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
} }
subprojects { subprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'com.diffplug.spotless' apply plugin: 'com.diffplug.spotless'
apply plugin: 'org.springframework.boot' apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management' apply plugin: 'io.spring.dependency-management'
@ -106,10 +100,6 @@ subprojects {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
} }
bootJar {
enabled = false
}
repositories { repositories {
mavenCentral() mavenCentral()
} }
@ -163,37 +153,6 @@ licenseReport {
allowedLicensesFile = new File("$projectDir/allowed-licenses.json") allowedLicensesFile = new File("$projectDir/allowed-licenses.json")
} }
sourceSets {
main {
java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('ADDITIONAL_FEATURES_OFF') == 'true'
|| (project.hasProperty('ADDITIONAL_FEATURES_OFF')
&& System.getProperty('ADDITIONAL_FEATURES_OFF') == 'true')) {
exclude 'stirling/software/proprietary/security/**'
}
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**'
}
}
}
test {
java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('ADDITIONAL_FEATURES_OFF') == 'true'
|| (project.hasProperty('ADDITIONAL_FEATURES_OFF')
&& System.getProperty('ADDITIONAL_FEATURES_OFF') == 'true')) {
exclude 'stirling/software/proprietary/security/**'
}
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**'
}
}
}
}
openApi { openApi {
apiDocsUrl = "http://localhost:8080/v1/api-docs" apiDocsUrl = "http://localhost:8080/v1/api-docs"
outputDir = file("$projectDir") outputDir = file("$projectDir")
@ -475,11 +434,9 @@ launch4j {
spotless { spotless {
java { java {
target sourceSets.main.allJava target project.fileTree('src').include('**/*.java')
target project(':common').sourceSets.main.allJava
target project(':proprietary').sourceSets.main.allJava
googleJavaFormat("1.27.0").aosp().reorderImports(false) googleJavaFormat("1.26.0").aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
toggleOffOn() toggleOffOn()

View File

@ -1,21 +1,30 @@
plugins {
id 'java-library'
}
bootJar {
enabled = false
}
dependencies { dependencies {
api 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1' implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
api 'com.fathzer:javaluator:3.0.6' implementation 'com.fathzer:javaluator:3.0.6'
api 'com.posthog.java:posthog:1.2.0' implementation 'com.posthog.java:posthog:1.2.0'
api 'io.github.pixee:java-security-toolkit:1.2.1' implementation 'io.github.pixee:java-security-toolkit:1.2.1'
api 'org.apache.commons:commons-lang3:3.17.0' implementation 'org.apache.commons:commons-lang3:3.17.0'
api 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor implementation 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8' implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
api "org.apache.pdfbox:pdfbox:$pdfboxVersion" implementation "org.apache.pdfbox:pdfbox:$pdfboxVersion"
api 'jakarta.servlet:jakarta.servlet-api:6.1.0' implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
api 'org.snakeyaml:snakeyaml-engine:2.9' implementation 'org.snakeyaml:snakeyaml-engine:2.9'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8" implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6"
compileOnly "org.projectlombok:lombok:$lombokVersion" compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion" annotationProcessor "org.projectlombok:lombok:$lombokVersion"
testImplementation "org.springframework.boot:spring-boot-starter-test" testImplementation "org.springframework.boot:spring-boot-starter-test"
testRuntimeOnly 'org.springframework.boot:spring-boot-starter-data-jpa'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0' testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
} }

View File

@ -1,7 +1,5 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -10,9 +8,10 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.function.Predicate; import java.util.function.Predicate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -24,11 +23,6 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine; import org.thymeleaf.spring6.SpringTemplateEngine;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
@Lazy @Lazy
@ -66,6 +60,11 @@ public class AppConfig {
return applicationProperties.getSecurity().getEnableLogin(); return applicationProperties.getSecurity().getEnableLogin();
} }
@Bean
public boolean disableSecurity() {
return env.getProperty("WITHOUT_ENHANCED_FEATURES", Boolean.class, false);
}
@Bean(name = "appName") @Bean(name = "appName")
public String appName() { public String appName() {
String homeTitle = applicationProperties.getUi().getAppName(); String homeTitle = applicationProperties.getUi().getAppName();
@ -149,22 +148,8 @@ public class AppConfig {
} }
} }
@Bean(name = "activeSecurity") @Bean(name = "missingActiveSecurity") // todo: may not be needed anymore
public boolean activeSecurity() { @ConditionalOnMissingClass("stirling.software.proprietary.security.SecurityConfiguration")
String additionalFeaturesOff = env.getProperty("ADDITIONAL_FEATURES_OFF");
if (additionalFeaturesOff != null) {
// ADDITIONAL_FEATURES_OFF=true means security OFF, so return false
// ADDITIONAL_FEATURES_OFF=false means security ON, so return true
return !Boolean.parseBoolean(additionalFeaturesOff);
}
return env.getProperty("DOCKER_ENABLE_SECURITY", Boolean.class, true);
}
@Bean(name = "missingActiveSecurity")
@ConditionalOnMissingClass(
"stirling.software.proprietary.security.configuration.SecurityConfiguration")
public boolean missingActiveSecurity() { public boolean missingActiveSecurity() {
return true; return true;
} }

View File

@ -10,7 +10,6 @@ import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver; import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
import org.thymeleaf.templateresource.FileTemplateResource; import org.thymeleaf.templateresource.FileTemplateResource;
import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.ITemplateResource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.InputStreamTemplateResource; import stirling.software.common.model.InputStreamTemplateResource;

View File

@ -48,22 +48,22 @@ public class InstallationPathConfig {
String os = System.getProperty("os.name").toLowerCase(); String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) { if (os.contains("win")) {
return Paths.get( return Paths.get(
System.getenv("APPDATA"), // parent path System.getenv("APPDATA"), // parent path
"Stirling-PDF") "Stirling-PDF")
+ File.separator; + File.separator;
} else if (os.contains("mac")) { } else if (os.contains("mac")) {
return Paths.get( return Paths.get(
System.getProperty("user.home"), System.getProperty("user.home"),
"Library", "Library",
"Application Support", "Application Support",
"Stirling-PDF") "Stirling-PDF")
+ File.separator; + File.separator;
} else { } else {
return Paths.get( return Paths.get(
System.getProperty("user.home"), // parent path System.getProperty("user.home"), // parent path
".config", ".config",
"Stirling-PDF") "Stirling-PDF")
+ File.separator; + File.separator;
} }
} }
return "." + File.separator; return "." + File.separator;

View File

@ -1,5 +1,6 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import java.io.IOException;
import java.util.Properties; import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
@ -11,7 +12,8 @@ import org.springframework.core.io.support.PropertySourceFactory;
public class YamlPropertySourceFactory implements PropertySourceFactory { public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override @Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) { public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource()); factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject(); Properties properties = factory.getObject();

View File

@ -12,9 +12,14 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
@ -24,29 +29,50 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.configuration.YamlPropertySourceFactory; import stirling.software.common.configuration.YamlPropertySourceFactory;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.common.model.oauth2.GitHubProvider; import stirling.software.common.util.ValidationUtil;
import stirling.software.common.model.oauth2.GoogleProvider; import stirling.software.common.model.oauth2.provider.GitHubProvider;
import stirling.software.common.model.oauth2.KeycloakProvider; import stirling.software.common.model.oauth2.provider.GoogleProvider;
import stirling.software.common.model.oauth2.Provider; import stirling.software.common.model.oauth2.provider.KeycloakProvider;
import stirling.software.common.util.ValidationUtils; import stirling.software.common.model.oauth2.provider.Provider;
import static stirling.software.common.util.ValidationUtil.isCollectionEmpty;
import static stirling.software.common.util.ValidationUtil.isStringEmpty;
@Data @Configuration
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConfigurationProperties(prefix = "") @ConfigurationProperties(prefix = "")
@Data
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class ApplicationProperties { public class ApplicationProperties {
@Bean
public PropertySource<?> dynamicYamlPropertySource(ConfigurableEnvironment environment)
throws IOException {
String configPath = InstallationPathConfig.getSettingsPath();
log.info("Attempting to load settings from: " + configPath);
File file = new File(configPath);
if (!file.exists()) {
log.error("Warning: Settings file does not exist at: " + configPath);
}
Resource resource = new FileSystemResource(configPath);
if (!resource.exists()) {
throw new FileNotFoundException("Settings file not found at: " + configPath);
}
EncodedResource encodedResource = new EncodedResource(resource);
PropertySource<?> propertySource =
new YamlPropertySourceFactory().createPropertySource(null, encodedResource);
environment.getPropertySources().addFirst(propertySource);
log.info("Loaded properties: " + propertySource.getSource());
return propertySource;
}
private Legal legal = new Legal(); private Legal legal = new Legal();
private Security security = new Security(); private Security security = new Security();
private System system = new System(); private System system = new System();
@ -62,32 +88,6 @@ public class ApplicationProperties {
private AutoPipeline autoPipeline = new AutoPipeline(); private AutoPipeline autoPipeline = new AutoPipeline();
private ProcessExecutor processExecutor = new ProcessExecutor(); private ProcessExecutor processExecutor = new ProcessExecutor();
@Bean
public PropertySource<?> dynamicYamlPropertySource(ConfigurableEnvironment environment)
throws IOException {
String configPath = InstallationPathConfig.getSettingsPath();
log.debug("Attempting to load settings from: " + configPath);
File file = new File(configPath);
if (!file.exists()) {
log.error("Warning: Settings file does not exist at: " + configPath);
}
Resource resource = new FileSystemResource(configPath);
if (!resource.exists()) {
throw new FileNotFoundException("Settings file not found at: " + configPath);
}
EncodedResource encodedResource = new EncodedResource(resource);
PropertySource<?> propertySource =
new YamlPropertySourceFactory().createPropertySource(null, encodedResource);
environment.getPropertySources().addFirst(propertySource);
log.debug("Loaded properties: " + propertySource.getSource());
return propertySource;
}
@Data @Data
public static class AutoPipeline { public static class AutoPipeline {
private String outputFolder; private String outputFolder;
@ -138,19 +138,19 @@ public class ApplicationProperties {
public boolean isUserPass() { public boolean isUserPass() {
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()) return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString())); || loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
} }
public boolean isOauth2Active() { public boolean isOauth2Active() {
return (oauth2 != null return (oauth2 != null
&& oauth2.getEnabled() && oauth2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
} }
public boolean isSaml2Active() { public boolean isSaml2Active() {
return (saml2 != null return (saml2 != null
&& saml2.getEnabled() && saml2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
} }
@Data @Data
@ -179,7 +179,7 @@ public class ApplicationProperties {
public InputStream getIdpMetadataUri() throws IOException { public InputStream getIdpMetadataUri() throws IOException {
if (idpMetadataUri.startsWith("classpath:")) { if (idpMetadataUri.startsWith("classpath:")) {
return new ClassPathResource(idpMetadataUri.substring("classpath".length())) return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
.getInputStream(); .getInputStream();
} }
try { try {
URI uri = new URI(idpMetadataUri); URI uri = new URI(idpMetadataUri);
@ -234,7 +234,7 @@ public class ApplicationProperties {
public void setScopes(String scopes) { public void setScopes(String scopes) {
List<String> scopesList = List<String> scopesList =
Arrays.stream(scopes.split(",")).map(String::trim).toList(); Arrays.stream(scopes.split(",")).map(String::trim).toList();
this.scopes.addAll(scopesList); this.scopes.addAll(scopesList);
} }
@ -247,11 +247,11 @@ public class ApplicationProperties {
} }
public boolean isSettingsValid() { public boolean isSettingsValid() {
return !ValidationUtils.isStringEmpty(this.getIssuer()) return !isStringEmpty(this.getIssuer())
&& !ValidationUtils.isStringEmpty(this.getClientId()) && !isStringEmpty(this.getClientId())
&& !ValidationUtils.isStringEmpty(this.getClientSecret()) && !isStringEmpty(this.getClientSecret())
&& !ValidationUtils.isCollectionEmpty(this.getScopes()) && !isCollectionEmpty(this.getScopes())
&& !ValidationUtils.isStringEmpty(this.getUseAsUsername()); && !isStringEmpty(this.getUseAsUsername());
} }
@Data @Data
@ -266,11 +266,11 @@ public class ApplicationProperties {
case "github" -> getGithub(); case "github" -> getGithub();
case "keycloak" -> getKeycloak(); case "keycloak" -> getKeycloak();
default -> default ->
throw new UnsupportedProviderException( throw new UnsupportedProviderException(
"Logout from the provider " "Logout from the provider "
+ registrationId + registrationId
+ " is not supported. " + " is not supported. "
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); + "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
}; };
} }
} }
@ -344,11 +344,11 @@ public class ApplicationProperties {
@Override @Override
public String toString() { public String toString() {
return """ return """
Driver { Driver {
driverName='%s' driverName='%s'
} }
""" """
.formatted(driverName); .formatted(driverName);
} }
} }
@ -365,14 +365,14 @@ public class ApplicationProperties {
public String getHomeDescription() { public String getHomeDescription() {
return homeDescription != null && homeDescription.trim().length() > 0 return homeDescription != null && homeDescription.trim().length() > 0
? homeDescription ? homeDescription
: null; : null;
} }
public String getAppNameNavbar() { public String getAppNameNavbar() {
return appNameNavbar != null && appNameNavbar.trim().length() > 0 return appNameNavbar != null && appNameNavbar.trim().length() > 0
? appNameNavbar ? appNameNavbar
: null; : null;
} }
} }
@ -458,8 +458,8 @@ public class ApplicationProperties {
public String getProducer() { public String getProducer() {
return producer == null || producer.trim().isEmpty() return producer == null || producer.trim().isEmpty()
? "Stirling-PDF" ? "Stirling-PDF"
: producer; : producer;
} }
} }

View File

@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
@Data @Data

View File

@ -1,10 +1,8 @@
package stirling.software.common.model.oauth2; package stirling.software.common.model.oauth2.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -1,10 +1,8 @@
package stirling.software.common.model.oauth2; package stirling.software.common.model.oauth2.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -1,10 +1,8 @@
package stirling.software.common.model.oauth2; package stirling.software.common.model.oauth2.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -1,16 +1,13 @@
package stirling.software.common.model.oauth2; package stirling.software.common.model.oauth2.provider;
import static stirling.software.common.model.enumeration.UsernameAttribute.EMAIL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.exception.UnsupportedClaimException; import stirling.software.common.model.exception.UnsupportedClaimException;
import static stirling.software.common.model.enumeration.UsernameAttribute.EMAIL;
@Data @Data
@NoArgsConstructor @NoArgsConstructor

View File

@ -208,7 +208,7 @@ public class PostHogService {
// New environment variables // New environment variables
dockerMetrics.put("version_tag", System.getenv("VERSION_TAG")); dockerMetrics.put("version_tag", System.getenv("VERSION_TAG"));
dockerMetrics.put("additional_features_off", System.getenv("ADDITIONAL_FEATURES_OFF")); dockerMetrics.put("without_enhanced_features", System.getenv("WITHOUT_ENHANCED_FEATURES"));
dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER")); dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER"));
return dockerMetrics; return dockerMetrics;

View File

@ -17,7 +17,6 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
@Component @Component

View File

@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
@Slf4j @Slf4j
public class GeneralUtils { public class GeneralUtil {
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = Files.createTempFile("temp", null).toFile(); File tempFile = Files.createTempFile("temp", null).toFile();

View File

@ -34,7 +34,6 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@Slf4j @Slf4j
@ -85,7 +84,7 @@ public class PdfUtils {
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
String[] pageOrderArr = pagesToCheck.split(","); String[] pageOrderArr = pagesToCheck.split(",");
List<Integer> pageList = List<Integer> pageList =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); GeneralUtil.parsePageList(pageOrderArr, document.getNumberOfPages());
for (int pageNumber : pageList) { for (int pageNumber : pageList) {
PDPage page = document.getPage(pageNumber); PDPage page = document.getPage(pageNumber);
@ -101,7 +100,7 @@ public class PdfUtils {
throws IOException { throws IOException {
String[] pageOrderArr = pageNumbersToCheck.split(","); String[] pageOrderArr = pageNumbersToCheck.split(",");
List<Integer> pageList = List<Integer> pageList =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); GeneralUtil.parsePageList(pageOrderArr, document.getNumberOfPages());
for (int pageNumber : pageList) { for (int pageNumber : pageList) {
PDPage page = document.getPage(pageNumber); PDPage page = document.getPage(pageNumber);

View File

@ -1,11 +1,10 @@
package stirling.software.common.util; package stirling.software.common.util;
import static stirling.software.common.util.ValidationUtils.isCollectionEmpty; import stirling.software.common.model.oauth2.provider.Provider;
import static stirling.software.common.util.ValidationUtils.isStringEmpty; import static stirling.software.common.util.ValidationUtil.isCollectionEmpty;
import static stirling.software.common.util.ValidationUtil.isStringEmpty;
import stirling.software.common.model.oauth2.Provider; public class ProviderUtil {
public class ProviderUtils {
public static boolean validateProvider(Provider provider) { public static boolean validateProvider(Provider provider) {
if (provider == null) { if (provider == null) {

View File

@ -1,6 +1,6 @@
package stirling.software.common.util; package stirling.software.common.util;
public class RequestUriUtils { public class RequestUriUtil {
public static boolean isStaticResource(String requestURI) { public static boolean isStaticResource(String requestURI) {
return isStaticResource("", requestURI); return isStaticResource("", requestURI);

View File

@ -1,14 +0,0 @@
package stirling.software.common.util;
import java.util.Collection;
public class ValidationUtils {
public static boolean isStringEmpty(String input) {
return input == null || input.isBlank();
}
public static boolean isCollectionEmpty(Collection<String> input) {
return input == null || input.isEmpty();
}
}

View File

@ -23,7 +23,6 @@ import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.misc.HighContrastColorCombination; import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;

View File

@ -18,7 +18,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;
public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy { public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {

View File

@ -9,7 +9,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.security.RedactionArea; import stirling.software.common.model.api.security.RedactionArea;
@Slf4j @Slf4j
@ -25,7 +24,9 @@ public class StringToArrayListPropertyEditor extends PropertyEditorSupport {
} }
try { try {
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
TypeReference<ArrayList<RedactionArea>> typeRef = new TypeReference<>() {}; TypeReference<ArrayList<RedactionArea>> typeRef =
new TypeReference<>() {
};
List<RedactionArea> list = objectMapper.readValue(text, typeRef); List<RedactionArea> list = objectMapper.readValue(text, typeRef);
setValue(list); setValue(list);
} catch (Exception e) { } catch (Exception e) {

View File

@ -14,7 +14,8 @@ public class StringToMapPropertyEditor extends PropertyEditorSupport {
@Override @Override
public void setAsText(String text) throws IllegalArgumentException { public void setAsText(String text) throws IllegalArgumentException {
try { try {
TypeReference<HashMap<String, String>> typeRef = new TypeReference<>() {}; TypeReference<HashMap<String, String>> typeRef =
new TypeReference<>() {};
Map<String, String> map = objectMapper.readValue(text, typeRef); Map<String, String> map = objectMapper.readValue(text, typeRef);
setValue(map); setValue(map);
} catch (Exception e) { } catch (Exception e) {

View File

@ -3,6 +3,7 @@ package stirling.software.common.util;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -13,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -139,7 +140,7 @@ class CheckProgramInstallTest {
void testGetAvailablePythonCommand_WhenNoPythonIsAvailable() void testGetAvailablePythonCommand_WhenNoPythonIsAvailable()
throws IOException, InterruptedException { throws IOException, InterruptedException {
// Arrange // Arrange
when(mockExecutor.runCommandWithOutputHandling(anyList())) when(mockExecutor.runCommandWithOutputHandling(any(List.class)))
.thenThrow(new IOException("Command not found")); .thenThrow(new IOException("Command not found"));
// Act // Act
@ -167,7 +168,7 @@ class CheckProgramInstallTest {
String firstCall = CheckProgramInstall.getAvailablePythonCommand(); String firstCall = CheckProgramInstall.getAvailablePythonCommand();
// Change the mock to simulate a change in the environment // Change the mock to simulate a change in the environment
when(mockExecutor.runCommandWithOutputHandling(anyList())) when(mockExecutor.runCommandWithOutputHandling(any(List.class)))
.thenThrow(new IOException("Command not found")); .thenThrow(new IOException("Command not found"));
String secondCall = CheckProgramInstall.getAvailablePythonCommand(); String secondCall = CheckProgramInstall.getAvailablePythonCommand();

View File

@ -6,152 +6,152 @@ import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class GeneralUtilsTest { public class GeneralUtilTest {
@Test @Test
void testParsePageListWithAll() { void testParsePageListWithAll() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"all"}, 5, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"all"}, 5, false);
assertEquals(List.of(0, 1, 2, 3, 4), result, "'All' keyword should return all pages."); assertEquals(List.of(0, 1, 2, 3, 4), result, "'All' keyword should return all pages.");
} }
@Test @Test
void testParsePageListWithAllOneBased() { void testParsePageListWithAllOneBased() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"all"}, 5, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"all"}, 5, true);
assertEquals(List.of(1, 2, 3, 4, 5), result, "'All' keyword should return all pages."); assertEquals(List.of(1, 2, 3, 4, 5), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFunc() { void nFunc() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"n"}, 5, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"n"}, 5, true);
assertEquals(List.of(1, 2, 3, 4, 5), result, "'n' keyword should return all pages."); assertEquals(List.of(1, 2, 3, 4, 5), result, "'n' keyword should return all pages.");
} }
@Test @Test
void nFuncAdvanced() { void nFuncAdvanced() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n"}, 9, true);
// skip 0 as not valid // skip 0 as not valid
assertEquals(List.of(4, 8), result, "'All' keyword should return all pages."); assertEquals(List.of(4, 8), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFuncAdvancedZero() { void nFuncAdvancedZero() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n"}, 9, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n"}, 9, false);
// skip 0 as not valid // skip 0 as not valid
assertEquals(List.of(3, 7), result, "'All' keyword should return all pages."); assertEquals(List.of(3, 7), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFuncAdvanced2() { void nFuncAdvanced2() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n-1"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n-1"}, 9, true);
// skip -1 as not valid // skip -1 as not valid
assertEquals(List.of(3, 7), result, "4n-1 should do (0-1), (4-1), (8-1)"); assertEquals(List.of(3, 7), result, "4n-1 should do (0-1), (4-1), (8-1)");
} }
@Test @Test
void nFuncAdvanced3() { void nFuncAdvanced3() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n+1"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n+1"}, 9, true);
assertEquals(List.of(5, 9), result, "'All' keyword should return all pages."); assertEquals(List.of(5, 9), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFunc_spaces() { void nFunc_spaces() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"n + 1"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"n + 1"}, 9, true);
assertEquals(List.of(2, 3, 4, 5, 6, 7, 8, 9), result); assertEquals(List.of(2, 3, 4, 5, 6, 7, 8, 9), result);
} }
@Test @Test
void nFunc_consecutive_Ns_nnn() { void nFunc_consecutive_Ns_nnn() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"nnn"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"nnn"}, 9, true);
assertEquals(List.of(1, 8), result); assertEquals(List.of(1, 8), result);
} }
@Test @Test
void nFunc_consecutive_Ns_nn() { void nFunc_consecutive_Ns_nn() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"nn"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"nn"}, 9, true);
assertEquals(List.of(1, 4, 9), result); assertEquals(List.of(1, 4, 9), result);
} }
@Test @Test
void nFunc_opening_closing_round_brackets() { void nFunc_opening_closing_round_brackets() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"(n-1)(n-2)"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"(n-1)(n-2)"}, 9, true);
assertEquals(List.of(2, 6), result); assertEquals(List.of(2, 6), result);
} }
@Test @Test
void nFunc_opening_round_brackets() { void nFunc_opening_round_brackets() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"2(n-1)"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"2(n-1)"}, 9, true);
assertEquals(List.of(2, 4, 6, 8), result); assertEquals(List.of(2, 4, 6, 8), result);
} }
@Test @Test
void nFunc_opening_round_brackets_n() { void nFunc_opening_round_brackets_n() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"n(n-1)"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"n(n-1)"}, 9, true);
assertEquals(List.of(2, 6), result); assertEquals(List.of(2, 6), result);
} }
@Test @Test
void nFunc_closing_round_brackets() { void nFunc_closing_round_brackets() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"(n-1)2"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"(n-1)2"}, 9, true);
assertEquals(List.of(2, 4, 6, 8), result); assertEquals(List.of(2, 4, 6, 8), result);
} }
@Test @Test
void nFunc_closing_round_brackets_n() { void nFunc_closing_round_brackets_n() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"(n-1)n"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"(n-1)n"}, 9, true);
assertEquals(List.of(2, 6), result); assertEquals(List.of(2, 6), result);
} }
@Test @Test
void nFunc_function_surrounded_with_brackets() { void nFunc_function_surrounded_with_brackets() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"(n-1)"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"(n-1)"}, 9, true);
assertEquals(List.of(1, 2, 3, 4, 5, 6, 7, 8), result); assertEquals(List.of(1, 2, 3, 4, 5, 6, 7, 8), result);
} }
@Test @Test
void nFuncAdvanced4() { void nFuncAdvanced4() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"3+2n"}, 9, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"3+2n"}, 9, true);
assertEquals(List.of(5, 7, 9), result, "'All' keyword should return all pages."); assertEquals(List.of(5, 7, 9), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFuncAdvancedZerobased() { void nFuncAdvancedZerobased() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n"}, 9, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n"}, 9, false);
assertEquals(List.of(3, 7), result, "'All' keyword should return all pages."); assertEquals(List.of(3, 7), result, "'All' keyword should return all pages.");
} }
@Test @Test
void nFuncAdvanced2Zerobased() { void nFuncAdvanced2Zerobased() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"4n-1"}, 9, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"4n-1"}, 9, false);
assertEquals(List.of(2, 6), result, "'All' keyword should return all pages."); assertEquals(List.of(2, 6), result, "'All' keyword should return all pages.");
} }
@Test @Test
void testParsePageListWithRangeOneBasedOutput() { void testParsePageListWithRangeOneBasedOutput() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1-3"}, 5, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"1-3"}, 5, true);
assertEquals(List.of(1, 2, 3), result, "Range should be parsed correctly."); assertEquals(List.of(1, 2, 3), result, "Range should be parsed correctly.");
} }
@Test @Test
void testParsePageListWithRangeZeroBaseOutput() { void testParsePageListWithRangeZeroBaseOutput() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1-3"}, 5, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"1-3"}, 5, false);
assertEquals(List.of(0, 1, 2), result, "Range should be parsed correctly."); assertEquals(List.of(0, 1, 2), result, "Range should be parsed correctly.");
} }
@Test @Test
void testParsePageListWithRangeOneBasedOutputFull() { void testParsePageListWithRangeOneBasedOutputFull() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 8, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"1,3,7-8"}, 8, true);
assertEquals(List.of(1, 3, 7, 8), result, "Range should be parsed correctly."); assertEquals(List.of(1, 3, 7, 8), result, "Range should be parsed correctly.");
} }
@Test @Test
void testParsePageListWithRangeOneBasedOutputFullOutOfRange() { void testParsePageListWithRangeOneBasedOutputFullOutOfRange() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 5, true); List<Integer> result = GeneralUtil.parsePageList(new String[] {"1,3,7-8"}, 5, true);
assertEquals(List.of(1, 3), result, "Range should be parsed correctly."); assertEquals(List.of(1, 3), result, "Range should be parsed correctly.");
} }
@Test @Test
void testParsePageListWithRangeZeroBaseOutputFull() { void testParsePageListWithRangeZeroBaseOutputFull() {
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 8, false); List<Integer> result = GeneralUtil.parsePageList(new String[] {"1,3,7-8"}, 8, false);
assertEquals(List.of(0, 2, 6, 7), result, "Range should be parsed correctly."); assertEquals(List.of(0, 2, 6, 7), result, "Range should be parsed correctly.");
} }
} }

View File

@ -4,38 +4,38 @@ import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class GeneralUtilsAdditionalTest { class GeneralUtilAdditionalTest {
@Test @Test
void testConvertSizeToBytes() { void testConvertSizeToBytes() {
assertEquals(1024L, GeneralUtils.convertSizeToBytes("1KB")); assertEquals(1024L, GeneralUtil.convertSizeToBytes("1KB"));
assertEquals(1024L * 1024, GeneralUtils.convertSizeToBytes("1MB")); assertEquals(1024L * 1024, GeneralUtil.convertSizeToBytes("1MB"));
assertEquals(1024L * 1024 * 1024, GeneralUtils.convertSizeToBytes("1GB")); assertEquals(1024L * 1024 * 1024, GeneralUtil.convertSizeToBytes("1GB"));
assertEquals(100L * 1024 * 1024, GeneralUtils.convertSizeToBytes("100")); assertEquals(100L * 1024 * 1024, GeneralUtil.convertSizeToBytes("100"));
assertNull(GeneralUtils.convertSizeToBytes("invalid")); assertNull(GeneralUtil.convertSizeToBytes("invalid"));
assertNull(GeneralUtils.convertSizeToBytes(null)); assertNull(GeneralUtil.convertSizeToBytes(null));
} }
@Test @Test
void testFormatBytes() { void testFormatBytes() {
assertEquals("512 B", GeneralUtils.formatBytes(512)); assertEquals("512 B", GeneralUtil.formatBytes(512));
assertEquals("1.00 KB", GeneralUtils.formatBytes(1024)); assertEquals("1.00 KB", GeneralUtil.formatBytes(1024));
assertEquals("1.00 MB", GeneralUtils.formatBytes(1024L * 1024)); assertEquals("1.00 MB", GeneralUtil.formatBytes(1024L * 1024));
assertEquals("1.00 GB", GeneralUtils.formatBytes(1024L * 1024 * 1024)); assertEquals("1.00 GB", GeneralUtil.formatBytes(1024L * 1024 * 1024));
} }
@Test @Test
void testURLHelpersAndUUID() { void testURLHelpersAndUUID() {
assertTrue(GeneralUtils.isValidURL("https://example.com")); assertTrue(GeneralUtil.isValidURL("https://example.com"));
assertFalse(GeneralUtils.isValidURL("htp:/bad")); assertFalse(GeneralUtil.isValidURL("htp:/bad"));
assertFalse(GeneralUtils.isURLReachable("http://localhost")); assertFalse(GeneralUtil.isURLReachable("http://localhost"));
assertFalse(GeneralUtils.isURLReachable("ftp://example.com")); assertFalse(GeneralUtil.isURLReachable("ftp://example.com"));
assertTrue(GeneralUtils.isValidUUID("123e4567-e89b-12d3-a456-426614174000")); assertTrue(GeneralUtil.isValidUUID("123e4567-e89b-12d3-a456-426614174000"));
assertFalse(GeneralUtils.isValidUUID("not-a-uuid")); assertFalse(GeneralUtil.isValidUUID("not-a-uuid"));
assertFalse(GeneralUtils.isVersionHigher(null, "1.0")); assertFalse(GeneralUtil.isVersionHigher(null, "1.0"));
assertTrue(GeneralUtils.isVersionHigher("2.0", "1.9")); assertTrue(GeneralUtil.isVersionHigher("2.0", "1.9"));
assertFalse(GeneralUtils.isVersionHigher("1.0", "1.0.1")); assertFalse(GeneralUtil.isVersionHigher("1.0", "1.0.1"));
} }
} }

View File

@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -133,7 +132,7 @@ class PDFToFileTest {
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)) .when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
.thenReturn(mockProcessExecutor); .thenReturn(mockProcessExecutor);
when(mockProcessExecutor.runCommandWithOutputHandling(anyList(), any(File.class))) when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
.thenAnswer( .thenAnswer(
invocation -> { invocation -> {
// When command is executed, simulate creation of output files // When command is executed, simulate creation of output files
@ -176,7 +175,7 @@ class PDFToFileTest {
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)) .when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
.thenReturn(mockProcessExecutor); .thenReturn(mockProcessExecutor);
when(mockProcessExecutor.runCommandWithOutputHandling(anyList(), any(File.class))) when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
.thenAnswer( .thenAnswer(
invocation -> { invocation -> {
// When command is executed, simulate creation of output files // When command is executed, simulate creation of output files
@ -252,7 +251,7 @@ class PDFToFileTest {
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)) .when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
.thenReturn(mockProcessExecutor); .thenReturn(mockProcessExecutor);
when(mockProcessExecutor.runCommandWithOutputHandling(anyList(), any(File.class))) when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
.thenAnswer( .thenAnswer(
invocation -> { invocation -> {
// When command is executed, simulate creation of output files // When command is executed, simulate creation of output files
@ -538,7 +537,7 @@ class PDFToFileTest {
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)) .when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
.thenReturn(mockProcessExecutor); .thenReturn(mockProcessExecutor);
when(mockProcessExecutor.runCommandWithOutputHandling(anyList())) when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class)))
.thenAnswer( .thenAnswer(
invocation -> { invocation -> {
// When command is executed, find the output directory argument // When command is executed, find the output directory argument

View File

@ -10,15 +10,13 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.oauth2.GitHubProvider; import stirling.software.common.model.oauth2.provider.GitHubProvider;
import stirling.software.common.model.oauth2.GoogleProvider; import stirling.software.common.model.oauth2.provider.GoogleProvider;
import stirling.software.common.model.oauth2.Provider; import stirling.software.common.model.oauth2.provider.Provider;
import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ProviderUtilsTest { class ProviderUtilTest {
@Test @Test
void testSuccessfulValidation() { void testSuccessfulValidation() {
@ -28,19 +26,19 @@ class ProviderUtilsTest {
when(provider.getClientSecret()).thenReturn("clientSecret"); when(provider.getClientSecret()).thenReturn("clientSecret");
when(provider.getScopes()).thenReturn(List.of("read:user")); when(provider.getScopes()).thenReturn(List.of("read:user"));
assertTrue(ProviderUtils.validateProvider(provider)); Assertions.assertTrue(ProviderUtil.validateProvider(provider));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("providerParams") @MethodSource("providerParams")
void testUnsuccessfulValidation(Provider provider) { void testUnsuccessfulValidation(Provider provider) {
assertFalse(ProviderUtils.validateProvider(provider)); Assertions.assertFalse(ProviderUtil.validateProvider(provider));
} }
public static Stream<Arguments> providerParams() { public static Stream<Arguments> providerParams() {
Provider generic = null; Provider generic = null;
var google = var google =
new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL); new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN); var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN);
return Stream.of(Arguments.of(generic), Arguments.of(google), Arguments.of(github)); return Stream.of(Arguments.of(generic), Arguments.of(google), Arguments.of(github));

View File

@ -0,0 +1,311 @@
package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class RequestUriUtilTest {
@Test
void testIsStaticResource() {
// Test static resources without context path
assertTrue(
RequestUriUtil.isStaticResource("/css/styles.css"), "CSS files should be static");
assertTrue(RequestUriUtil.isStaticResource("/js/script.js"), "JS files should be static");
assertTrue(
RequestUriUtil.isStaticResource("/images/logo.png"),
"Image files should be static");
assertTrue(
RequestUriUtil.isStaticResource("/public/index.html"),
"Public files should be static");
assertTrue(
RequestUriUtil.isStaticResource("/pdfjs/pdf.worker.js"),
"PDF.js files should be static");
assertTrue(
RequestUriUtil.isStaticResource("/api/v1/info/status"),
"API status should be static");
assertTrue(
RequestUriUtil.isStaticResource("/some-path/icon.svg"),
"SVG files should be static");
assertTrue(RequestUriUtil.isStaticResource("/login"), "Login page should be static");
assertTrue(RequestUriUtil.isStaticResource("/error"), "Error page should be static");
// Test non-static resources
assertFalse(
RequestUriUtil.isStaticResource("/api/v1/users"),
"API users should not be static");
assertFalse(
RequestUriUtil.isStaticResource("/api/v1/orders"),
"API orders should not be static");
assertFalse(RequestUriUtil.isStaticResource("/"), "Root path should not be static");
assertFalse(
RequestUriUtil.isStaticResource("/register"),
"Register page should not be static");
assertFalse(
RequestUriUtil.isStaticResource("/api/v1/products"),
"API products should not be static");
}
@Test
void testIsStaticResourceWithContextPath() {
String contextPath = "/myapp";
// Test static resources with context path
assertTrue(
RequestUriUtil.isStaticResource(contextPath, contextPath + "/css/styles.css"),
"CSS with context path should be static");
assertTrue(
RequestUriUtil.isStaticResource(contextPath, contextPath + "/js/script.js"),
"JS with context path should be static");
assertTrue(
RequestUriUtil.isStaticResource(contextPath, contextPath + "/images/logo.png"),
"Images with context path should be static");
assertTrue(
RequestUriUtil.isStaticResource(contextPath, contextPath + "/login"),
"Login with context path should be static");
// Test non-static resources with context path
assertFalse(
RequestUriUtil.isStaticResource(contextPath, contextPath + "/api/v1/users"),
"API users with context path should not be static");
assertFalse(
RequestUriUtil.isStaticResource(contextPath, "/"),
"Root path with context path should not be static");
}
@ParameterizedTest
@ValueSource(
strings = {
"robots.txt",
"/favicon.ico",
"/icon.svg",
"/image.png",
"/site.webmanifest",
"/app/logo.svg",
"/downloads/document.png",
"/assets/brand.ico",
"/any/path/with/image.svg",
"/deep/nested/folder/icon.png"
})
void testIsStaticResourceWithFileExtensions(String path) {
assertTrue(
RequestUriUtil.isStaticResource(path),
"Files with specific extensions should be static regardless of path");
}
@Test
void testIsTrackableResource() {
// Test non-trackable resources (returns false)
assertFalse(
RequestUriUtil.isTrackableResource("/js/script.js"),
"JS files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/v1/api-docs"),
"API docs should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("robots.txt"),
"robots.txt should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/images/logo.png"),
"Images should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/styles.css"),
"CSS files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/script.js.map"),
"Map files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/icon.svg"),
"SVG files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/popularity.txt"),
"Popularity file should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/script.js"),
"JS files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/swagger/index.html"),
"Swagger files should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/api/v1/info/status"),
"API info should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/site.webmanifest"),
"Webmanifest should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/fonts/font.woff"),
"Fonts should not be trackable");
assertFalse(
RequestUriUtil.isTrackableResource("/pdfjs/viewer.js"),
"PDF.js files should not be trackable");
// Test trackable resources (returns true)
assertTrue(RequestUriUtil.isTrackableResource("/login"), "Login page should be trackable");
assertTrue(
RequestUriUtil.isTrackableResource("/register"),
"Register page should be trackable");
assertTrue(
RequestUriUtil.isTrackableResource("/api/v1/users"),
"API users should be trackable");
assertTrue(RequestUriUtil.isTrackableResource("/"), "Root path should be trackable");
assertTrue(
RequestUriUtil.isTrackableResource("/some-other-path"),
"Other paths should be trackable");
}
@Test
void testIsTrackableResourceWithContextPath() {
String contextPath = "/myapp";
// Test with context path
assertFalse(
RequestUriUtil.isTrackableResource(contextPath, "/js/script.js"),
"JS files should not be trackable with context path");
assertTrue(
RequestUriUtil.isTrackableResource(contextPath, "/login"),
"Login page should be trackable with context path");
// Additional tests with context path
assertFalse(
RequestUriUtil.isTrackableResource(contextPath, "/fonts/custom.woff"),
"Font files should not be trackable with context path");
assertFalse(
RequestUriUtil.isTrackableResource(contextPath, "/images/header.png"),
"Images should not be trackable with context path");
assertFalse(
RequestUriUtil.isTrackableResource(contextPath, "/swagger/ui.html"),
"Swagger UI should not be trackable with context path");
assertTrue(
RequestUriUtil.isTrackableResource(contextPath, "/account/profile"),
"Account page should be trackable with context path");
assertTrue(
RequestUriUtil.isTrackableResource(contextPath, "/pdf/view"),
"PDF view page should be trackable with context path");
}
@ParameterizedTest
@ValueSource(
strings = {
"/js/util.js",
"/v1/api-docs/swagger.json",
"/robots.txt",
"/images/header/logo.png",
"/styles/theme.css",
"/build/app.js.map",
"/assets/icon.svg",
"/data/popularity.txt",
"/bundle.js",
"/api/swagger-ui.html",
"/api/v1/info/health",
"/site.webmanifest",
"/fonts/roboto.woff",
"/pdfjs/viewer.js"
})
void testNonTrackableResources(String path) {
assertFalse(
RequestUriUtil.isTrackableResource(path),
"Resources matching patterns should not be trackable: " + path);
}
@ParameterizedTest
@ValueSource(
strings = {
"/",
"/home",
"/login",
"/register",
"/pdf/merge",
"/pdf/split",
"/api/v1/users/1",
"/api/v1/documents/process",
"/settings",
"/account/profile",
"/dashboard",
"/help",
"/about"
})
void testTrackableResources(String path) {
assertTrue(
RequestUriUtil.isTrackableResource(path),
"App routes should be trackable: " + path);
}
@Test
void testEdgeCases() {
// Test with empty strings
assertFalse(RequestUriUtil.isStaticResource("", ""), "Empty path should not be static");
assertTrue(RequestUriUtil.isTrackableResource("", ""), "Empty path should be trackable");
// Test with null-like behavior (would actually throw NPE in real code)
// These are not actual null tests but shows handling of odd cases
assertFalse(RequestUriUtil.isStaticResource("null"), "String 'null' should not be static");
// Test String "null" as a path
boolean isTrackable = RequestUriUtil.isTrackableResource("null");
assertTrue(isTrackable, "String 'null' should be trackable");
// Mixed case extensions test - note that Java's endsWith() is case-sensitive
// We'll check actual behavior and document it rather than asserting
// Always test the lowercase versions which should definitely work
assertTrue(
RequestUriUtil.isStaticResource("/logo.png"), "PNG (lowercase) should be static");
assertTrue(
RequestUriUtil.isStaticResource("/icon.svg"), "SVG (lowercase) should be static");
// Path with query parameters
assertFalse(
RequestUriUtil.isStaticResource("/api/users?page=1"),
"Path with query params should respect base path");
assertTrue(
RequestUriUtil.isStaticResource("/images/logo.png?v=123"),
"Static resource with query params should still be static");
// Paths with fragments
assertTrue(
RequestUriUtil.isStaticResource("/css/styles.css#section1"),
"CSS with fragment should be static");
// Multiple dots in filename
assertTrue(
RequestUriUtil.isStaticResource("/js/jquery.min.js"),
"JS with multiple dots should be static");
// Special characters in path
assertTrue(
RequestUriUtil.isStaticResource("/images/user's-photo.png"),
"Path with special chars should be handled correctly");
}
@Test
void testComplexPaths() {
// Test complex static resource paths
assertTrue(
RequestUriUtil.isStaticResource("/css/theme/dark/styles.css"),
"Nested CSS should be static");
assertTrue(
RequestUriUtil.isStaticResource("/fonts/open-sans/bold/font.woff"),
"Nested font should be static");
assertTrue(
RequestUriUtil.isStaticResource("/js/vendor/jquery/3.5.1/jquery.min.js"),
"Versioned JS should be static");
// Test complex paths with context
String contextPath = "/app";
assertTrue(
RequestUriUtil.isStaticResource(
contextPath, contextPath + "/css/theme/dark/styles.css"),
"Nested CSS with context should be static");
// Test boundary cases for isTrackableResource
assertFalse(
RequestUriUtil.isTrackableResource("/js-framework/components"),
"Path starting with js- should not be treated as JS resource");
assertFalse(
RequestUriUtil.isTrackableResource("/fonts-selection"),
"Path starting with fonts- should not be treated as font resource");
}
}

View File

@ -1,311 +0,0 @@
package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class RequestUriUtilsTest {
@Test
void testIsStaticResource() {
// Test static resources without context path
assertTrue(
RequestUriUtils.isStaticResource("/css/styles.css"), "CSS files should be static");
assertTrue(RequestUriUtils.isStaticResource("/js/script.js"), "JS files should be static");
assertTrue(
RequestUriUtils.isStaticResource("/images/logo.png"),
"Image files should be static");
assertTrue(
RequestUriUtils.isStaticResource("/public/index.html"),
"Public files should be static");
assertTrue(
RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js"),
"PDF.js files should be static");
assertTrue(
RequestUriUtils.isStaticResource("/api/v1/info/status"),
"API status should be static");
assertTrue(
RequestUriUtils.isStaticResource("/some-path/icon.svg"),
"SVG files should be static");
assertTrue(RequestUriUtils.isStaticResource("/login"), "Login page should be static");
assertTrue(RequestUriUtils.isStaticResource("/error"), "Error page should be static");
// Test non-static resources
assertFalse(
RequestUriUtils.isStaticResource("/api/v1/users"),
"API users should not be static");
assertFalse(
RequestUriUtils.isStaticResource("/api/v1/orders"),
"API orders should not be static");
assertFalse(RequestUriUtils.isStaticResource("/"), "Root path should not be static");
assertFalse(
RequestUriUtils.isStaticResource("/register"),
"Register page should not be static");
assertFalse(
RequestUriUtils.isStaticResource("/api/v1/products"),
"API products should not be static");
}
@Test
void testIsStaticResourceWithContextPath() {
String contextPath = "/myapp";
// Test static resources with context path
assertTrue(
RequestUriUtils.isStaticResource(contextPath, contextPath + "/css/styles.css"),
"CSS with context path should be static");
assertTrue(
RequestUriUtils.isStaticResource(contextPath, contextPath + "/js/script.js"),
"JS with context path should be static");
assertTrue(
RequestUriUtils.isStaticResource(contextPath, contextPath + "/images/logo.png"),
"Images with context path should be static");
assertTrue(
RequestUriUtils.isStaticResource(contextPath, contextPath + "/login"),
"Login with context path should be static");
// Test non-static resources with context path
assertFalse(
RequestUriUtils.isStaticResource(contextPath, contextPath + "/api/v1/users"),
"API users with context path should not be static");
assertFalse(
RequestUriUtils.isStaticResource(contextPath, "/"),
"Root path with context path should not be static");
}
@ParameterizedTest
@ValueSource(
strings = {
"robots.txt",
"/favicon.ico",
"/icon.svg",
"/image.png",
"/site.webmanifest",
"/app/logo.svg",
"/downloads/document.png",
"/assets/brand.ico",
"/any/path/with/image.svg",
"/deep/nested/folder/icon.png"
})
void testIsStaticResourceWithFileExtensions(String path) {
assertTrue(
RequestUriUtils.isStaticResource(path),
"Files with specific extensions should be static regardless of path");
}
@Test
void testIsTrackableResource() {
// Test non-trackable resources (returns false)
assertFalse(
RequestUriUtils.isTrackableResource("/js/script.js"),
"JS files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/v1/api-docs"),
"API docs should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("robots.txt"),
"robots.txt should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/images/logo.png"),
"Images should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/styles.css"),
"CSS files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/script.js.map"),
"Map files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/icon.svg"),
"SVG files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/popularity.txt"),
"Popularity file should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/script.js"),
"JS files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/swagger/index.html"),
"Swagger files should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/api/v1/info/status"),
"API info should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/site.webmanifest"),
"Webmanifest should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/fonts/font.woff"),
"Fonts should not be trackable");
assertFalse(
RequestUriUtils.isTrackableResource("/pdfjs/viewer.js"),
"PDF.js files should not be trackable");
// Test trackable resources (returns true)
assertTrue(RequestUriUtils.isTrackableResource("/login"), "Login page should be trackable");
assertTrue(
RequestUriUtils.isTrackableResource("/register"),
"Register page should be trackable");
assertTrue(
RequestUriUtils.isTrackableResource("/api/v1/users"),
"API users should be trackable");
assertTrue(RequestUriUtils.isTrackableResource("/"), "Root path should be trackable");
assertTrue(
RequestUriUtils.isTrackableResource("/some-other-path"),
"Other paths should be trackable");
}
@Test
void testIsTrackableResourceWithContextPath() {
String contextPath = "/myapp";
// Test with context path
assertFalse(
RequestUriUtils.isTrackableResource(contextPath, "/js/script.js"),
"JS files should not be trackable with context path");
assertTrue(
RequestUriUtils.isTrackableResource(contextPath, "/login"),
"Login page should be trackable with context path");
// Additional tests with context path
assertFalse(
RequestUriUtils.isTrackableResource(contextPath, "/fonts/custom.woff"),
"Font files should not be trackable with context path");
assertFalse(
RequestUriUtils.isTrackableResource(contextPath, "/images/header.png"),
"Images should not be trackable with context path");
assertFalse(
RequestUriUtils.isTrackableResource(contextPath, "/swagger/ui.html"),
"Swagger UI should not be trackable with context path");
assertTrue(
RequestUriUtils.isTrackableResource(contextPath, "/account/profile"),
"Account page should be trackable with context path");
assertTrue(
RequestUriUtils.isTrackableResource(contextPath, "/pdf/view"),
"PDF view page should be trackable with context path");
}
@ParameterizedTest
@ValueSource(
strings = {
"/js/util.js",
"/v1/api-docs/swagger.json",
"/robots.txt",
"/images/header/logo.png",
"/styles/theme.css",
"/build/app.js.map",
"/assets/icon.svg",
"/data/popularity.txt",
"/bundle.js",
"/api/swagger-ui.html",
"/api/v1/info/health",
"/site.webmanifest",
"/fonts/roboto.woff",
"/pdfjs/viewer.js"
})
void testNonTrackableResources(String path) {
assertFalse(
RequestUriUtils.isTrackableResource(path),
"Resources matching patterns should not be trackable: " + path);
}
@ParameterizedTest
@ValueSource(
strings = {
"/",
"/home",
"/login",
"/register",
"/pdf/merge",
"/pdf/split",
"/api/v1/users/1",
"/api/v1/documents/process",
"/settings",
"/account/profile",
"/dashboard",
"/help",
"/about"
})
void testTrackableResources(String path) {
assertTrue(
RequestUriUtils.isTrackableResource(path),
"App routes should be trackable: " + path);
}
@Test
void testEdgeCases() {
// Test with empty strings
assertFalse(RequestUriUtils.isStaticResource("", ""), "Empty path should not be static");
assertTrue(RequestUriUtils.isTrackableResource("", ""), "Empty path should be trackable");
// Test with null-like behavior (would actually throw NPE in real code)
// These are not actual null tests but shows handling of odd cases
assertFalse(RequestUriUtils.isStaticResource("null"), "String 'null' should not be static");
// Test String "null" as a path
boolean isTrackable = RequestUriUtils.isTrackableResource("null");
assertTrue(isTrackable, "String 'null' should be trackable");
// Mixed case extensions test - note that Java's endsWith() is case-sensitive
// We'll check actual behavior and document it rather than asserting
// Always test the lowercase versions which should definitely work
assertTrue(
RequestUriUtils.isStaticResource("/logo.png"), "PNG (lowercase) should be static");
assertTrue(
RequestUriUtils.isStaticResource("/icon.svg"), "SVG (lowercase) should be static");
// Path with query parameters
assertFalse(
RequestUriUtils.isStaticResource("/api/users?page=1"),
"Path with query params should respect base path");
assertTrue(
RequestUriUtils.isStaticResource("/images/logo.png?v=123"),
"Static resource with query params should still be static");
// Paths with fragments
assertTrue(
RequestUriUtils.isStaticResource("/css/styles.css#section1"),
"CSS with fragment should be static");
// Multiple dots in filename
assertTrue(
RequestUriUtils.isStaticResource("/js/jquery.min.js"),
"JS with multiple dots should be static");
// Special characters in path
assertTrue(
RequestUriUtils.isStaticResource("/images/user's-photo.png"),
"Path with special chars should be handled correctly");
}
@Test
void testComplexPaths() {
// Test complex static resource paths
assertTrue(
RequestUriUtils.isStaticResource("/css/theme/dark/styles.css"),
"Nested CSS should be static");
assertTrue(
RequestUriUtils.isStaticResource("/fonts/open-sans/bold/font.woff"),
"Nested font should be static");
assertTrue(
RequestUriUtils.isStaticResource("/js/vendor/jquery/3.5.1/jquery.min.js"),
"Versioned JS should be static");
// Test complex paths with context
String contextPath = "/app";
assertTrue(
RequestUriUtils.isStaticResource(
contextPath, contextPath + "/css/theme/dark/styles.css"),
"Nested CSS with context should be static");
// Test boundary cases for isTrackableResource
assertFalse(
RequestUriUtils.isTrackableResource("/js-framework/components"),
"Path starting with js- should not be treated as JS resource");
assertFalse(
RequestUriUtils.isTrackableResource("/fonts-selection"),
"Path starting with fonts- should not be treated as font resource");
}
}

View File

@ -20,7 +20,7 @@ services:
- ./stirling/latest/logs:/logs:rw - ./stirling/latest/logs:/logs:rw
- ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw - ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002

View File

@ -20,7 +20,7 @@ services:
- ./stirling/latest/config:/configs:rw - ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw - ./stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002

View File

@ -18,7 +18,7 @@ services:
- ./stirling/latest/config:/configs:rw - ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw - ./stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002

View File

@ -18,7 +18,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
SECURITY_OAUTH2_ENABLED: "true" SECURITY_OAUTH2_ENABLED: "true"
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF

View File

@ -18,7 +18,7 @@ services:
- ./stirling/latest/config:/configs:rw - ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw - ./stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002

View File

@ -18,7 +18,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en-US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite UI_APPNAME: Stirling-PDF-Lite

View File

@ -17,7 +17,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "true" WITHOUT_ENHANCED_FEATURES: "true"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Ultra-lite UI_APPNAME: Stirling-PDF-Ultra-lite

View File

@ -18,7 +18,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "true" WITHOUT_ENHANCED_FEATURES: "true"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID" LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
SYSTEM_DEFAULTLOCALE: en-US SYSTEM_DEFAULTLOCALE: en-US

View File

@ -18,7 +18,7 @@ services:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
ADDITIONAL_FEATURES_OFF: "false" WITHOUT_ENHANCED_FEATURES: "false"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
PUID: 1002 PUID: 1002
PGID: 1002 PGID: 1002

View File

@ -1,51 +1,45 @@
Stirling PDF User License Stirling PDF Enterprise Edition (EE) license (the “EE License”)
Copyright (c) 2025 Stirling PDF Inc. Copyright (c) 2025-present Stirling Tools
License Scope & Usage Rights With regard to the Stirling PDF Software:
Production use of the Stirling PDF Software is only permitted with a valid Stirling PDF User License. This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Stirling PDF Terms of Service, available
at https://www.stirlingpdf.com/terms-and-conditions (the “EE Terms”), or other
agreement governing the use of the Software, as agreed by you and Stirling PDF,
and otherwise have a valid Stirling PDF Enterprise Edition subscription for the
correct number of user seats. Subject to the foregoing sentence, you are free to
modify this Software and publish patches to the Software. You agree that Stirling PDF
and/or its licensors (as applicable) retain all right, title and interest in and
to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Stirling PDF Enterprise Edition subscription for the correct
number of user seats. Notwithstanding the foregoing, you may copy and modify
the Software for development and testing purposes, without requiring a
subscription. You agree that Stirling PDF and/or its licensors (as applicable) retain
all right, title and interest in and to all such modifications. You are not
granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
For purposes of this license, “the Software” refers to the Stirling PDF application and any associated documentation files This EE License applies only to the part of this Software that is not
provided by Stirling PDF Inc. You or your organization may not use the Software in production, at scale, or for business-critical distributed as part of MIT License. Any part of this Software
processes unless you have agreed to, and remain in compliance with, the Stirling PDF Subscription Terms of Service distributed as part of MIT License or is served client-side as an image, font,
(https://www.stirlingpdf.com/terms) or another valid agreement with Stirling PDF, and hold an active User License subscription cascading stylesheet (CSS), file which produces or is compiled, arranged,
covering the appropriate number of licensed users. augmented, or combined into client-side JavaScript, in whole or in part, is
copyrighted under the MIT Expat license. The full text of this EE License shall
be included in all copies or substantial portions of the Software.
Trial and Minimal Use THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
You may use the Software without a paid subscription for the sole purposes of internal trial, evaluation, or minimal use, provided that: For all third party components incorporated into the Stirling PDF Software, those
* Use is limited to the capabilities and restrictions defined by the Software itself; components are licensed under the original license provided by the owner of the
* You do not copy, distribute, sublicense, reverse-engineer, or use the Software in client-facing or commercial contexts. applicable component.
Continued use beyond this scope requires a valid Stirling PDF User License.
Modifications and Derivative Works
You may modify the Software only for development or internal testing purposes. Any such modifications or derivative works:
* May not be deployed in production environments without a valid User License;
* May not be distributed or sublicensed;
* Remain the intellectual property of Stirling PDF and/or its licensors;
* May only be used, copied, or exploited in accordance with the terms of a valid Stirling PDF User License subscription.
Prohibited Actions
Unless explicitly permitted by a paid license or separate agreement, you may not:
* Use the Software in production environments;
* Copy, merge, distribute, sublicense, or sell the Software;
* Remove or alter any licensing or copyright notices;
* Circumvent access restrictions or licensing requirements.
Third-Party Components
The Stirling PDF Software may include components subject to separate open source licenses. Such components remain governed by
their original license terms as provided by their respective owners.
Disclaimer
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,29 +1,41 @@
plugins {
id 'java-library'
}
repositories { repositories {
maven { url = "https://build.shibboleth.net/maven/releases" } maven { url = "https://build.shibboleth.net/maven/releases" }
} }
java {
sourceCompatibility = JavaVersion.VERSION_17
}
bootJar {
enabled = false
}
dependencies { dependencies {
implementation project(':common') implementation project(':common')
api 'org.springframework:spring-jdbc' implementation 'org.springframework.boot:spring-boot-starter-jetty'
api 'org.springframework:spring-webmvc' implementation 'io.swagger.core.v3:swagger-core-jakarta:2.2.30'
api 'org.springframework.session:spring-session-core'
api "org.springframework.security:spring-security-core:$springSecuritySamlVersion"
api "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
api 'org.springframework.boot:spring-boot-starter-jetty'
api 'org.springframework.boot:spring-boot-starter-security'
api 'org.springframework.boot:spring-boot-starter-data-jpa'
api 'org.springframework.boot:spring-boot-starter-oauth2-client'
api 'org.springframework.boot:spring-boot-starter-mail'
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.30'
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0' implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation 'org.bouncycastle:bcprov-jdk18on:1.80' implementation 'org.bouncycastle:bcprov-jdk18on:1.80'
implementation 'io.github.pixee:java-security-toolkit:1.2.1' implementation 'org.springframework:spring-jdbc:6.2.6'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation "org.springframework.security:spring-security-core:$springSecuritySamlVersion"
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
implementation 'org.springframework.session:spring-session-core:3.4.3'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE'
api 'io.micrometer:micrometer-registry-prometheus' implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database
runtimeOnly 'org.postgresql:postgresql:42.7.5' runtimeOnly 'org.postgresql:postgresql:42.7.5'

View File

@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
proprietary/gradlew vendored Executable file
View File

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
proprietary/gradlew.bat vendored Normal file
View File

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,8 +1,11 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.InternalAuthenticationServiceException;
@ -10,13 +13,6 @@ import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,19 +1,15 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import stirling.software.common.util.RequestUriUtils; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import stirling.software.common.util.RequestUriUtil;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@ -50,7 +46,7 @@ public class CustomAuthenticationSuccessHandler
: null; : null;
if (savedRequest != null if (savedRequest != null
&& !RequestUriUtils.isStaticResource( && !RequestUriUtil.isStaticResource(
request.getContextPath(), savedRequest.getRedirectUrl())) { request.getContextPath(), savedRequest.getRedirectUrl())) {
// Redirect to the original destination // Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication); super.onAuthenticationSuccess(request, response, authentication);

View File

@ -1,32 +1,27 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import com.coveo.saml.SamlClient;
import com.coveo.saml.SamlException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import com.coveo.saml.SamlClient;
import com.coveo.saml.SamlException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.AppConfig; import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;
import stirling.software.common.model.oauth2.KeycloakProvider; import stirling.software.common.model.oauth2.provider.KeycloakProvider;
import stirling.software.common.util.UrlUtils; import stirling.software.common.util.UrlUtils;
import stirling.software.proprietary.security.saml2.CertificateUtils; import stirling.software.proprietary.security.saml2.CertificateUtils;
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
@ -38,7 +33,6 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
public static final String LOGOUT_PATH = "/login?logout=true"; public static final String LOGOUT_PATH = "/login?logout=true";
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final AppConfig appConfig; private final AppConfig appConfig;
@Override @Override
@ -177,7 +171,9 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private SamlClient getSamlClient( private SamlClient getSamlClient(
String registrationId, SAML2 samlConf, List<X509Certificate> certificates) String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
throws SamlException { throws SamlException {
String serverUrl = appConfig.getBaseUrl() + ":" + appConfig.getServerPort(); // fixMe: move to a config class
String serverUrl =
appConfig.getBaseUrl() + ":" + appConfig.getServerPort();
String relyingPartyIdentifier = String relyingPartyIdentifier =
serverUrl + "/saml2/service-provider-metadata/" + registrationId; serverUrl + "/saml2/service-provider-metadata/" + registrationId;

View File

@ -1,18 +1,14 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import jakarta.annotation.PostConstruct;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.common.model.enumeration.Role;
import stirling.software.proprietary.security.service.DatabaseServiceInterface; import stirling.software.proprietary.security.service.DatabaseServiceInterface;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,10 +1,8 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import stirling.software.proprietary.security.filter.IPRateLimitingFilter; import stirling.software.proprietary.security.filter.IPRateLimitingFilter;
@Component @Component

View File

@ -1,19 +1,15 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import javax.sql.DataSource; import javax.sql.DataSource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
@ -58,27 +54,14 @@ public class DatabaseConfig {
public DataSource dataSource() throws UnsupportedProviderException { public DataSource dataSource() throws UnsupportedProviderException {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create(); DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
if (!runningProOrHigher || !datasource.isEnableCustomDatabase()) { if (!runningProOrHigher) {
return useDefaultDataSource(dataSourceBuilder); return useDefaultDataSource(dataSourceBuilder);
} }
return useCustomDataSource(dataSourceBuilder); if (!datasource.isEnableCustomDatabase()) {
} return useDefaultDataSource(dataSourceBuilder);
}
private DataSource useDefaultDataSource(DataSourceBuilder<?> dataSourceBuilder) {
log.info("Using default H2 database");
dataSourceBuilder
.url(DATASOURCE_DEFAULT_URL)
.driverClassName(DatabaseDriver.H2.getDriverClassName())
.username(DEFAULT_USERNAME);
return dataSourceBuilder.build();
}
@ConditionalOnBooleanProperty(name = "premium.enabled")
private DataSource useCustomDataSource(DataSourceBuilder<?> dataSourceBuilder)
throws UnsupportedProviderException {
log.info("Using custom database configuration"); log.info("Using custom database configuration");
if (!datasource.getCustomDatabaseUrl().isBlank()) { if (!datasource.getCustomDatabaseUrl().isBlank()) {
@ -102,6 +85,16 @@ public class DatabaseConfig {
return dataSourceBuilder.build(); return dataSourceBuilder.build();
} }
private DataSource useDefaultDataSource(DataSourceBuilder<?> dataSourceBuilder) {
log.info("Using default H2 database");
dataSourceBuilder.url(DATASOURCE_DEFAULT_URL)
.driverClassName(DatabaseDriver.H2.getDriverClassName())
.username(DEFAULT_USERNAME);
return dataSourceBuilder.build();
}
/** /**
* Generate the URL the <code>DataSource</code> will use to connect to the database * Generate the URL the <code>DataSource</code> will use to connect to the database
* *

View File

@ -10,7 +10,6 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
/** /**

View File

@ -1,7 +1,7 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -26,10 +26,7 @@ import org.springframework.security.web.authentication.rememberme.PersistentToke
import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.AppConfig; import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.proprietary.security.CustomAuthenticationFailureHandler; import stirling.software.proprietary.security.CustomAuthenticationFailureHandler;
@ -52,10 +49,10 @@ import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
import stirling.software.proprietary.security.session.SessionPersistentRegistry; import stirling.software.proprietary.security.session.SessionPersistentRegistry;
@Slf4j
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity @EnableMethodSecurity
@Slf4j
@DependsOn("runningProOrHigher") @DependsOn("runningProOrHigher")
public class SecurityConfiguration { public class SecurityConfiguration {
@ -76,22 +73,22 @@ public class SecurityConfiguration {
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver; private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
public SecurityConfiguration( public SecurityConfiguration(
PersistentLoginRepository persistentLoginRepository, PersistentLoginRepository persistentLoginRepository,
CustomUserDetailsService userDetailsService, CustomUserDetailsService userDetailsService,
@Lazy UserService userService, @Lazy UserService userService,
@Qualifier("loginEnabled") boolean loginEnabledValue, @Qualifier("loginEnabled") boolean loginEnabledValue,
@Qualifier("runningProOrHigher") boolean runningProOrHigher, @Qualifier("runningProOrHigher") boolean runningProOrHigher,
AppConfig appConfig, AppConfig appConfig,
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
UserAuthenticationFilter userAuthenticationFilter, UserAuthenticationFilter userAuthenticationFilter,
LoginAttemptService loginAttemptService, LoginAttemptService loginAttemptService,
FirstLoginFilter firstLoginFilter, FirstLoginFilter firstLoginFilter,
SessionPersistentRegistry sessionRegistry, SessionPersistentRegistry sessionRegistry,
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper, @Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
@Autowired(required = false) @Autowired(required = false)
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations, RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
@Autowired(required = false) @Autowired(required = false)
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) { OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
this.userDetailsService = userDetailsService; this.userDetailsService = userDetailsService;
this.userService = userService; this.userService = userService;
this.loginEnabledValue = loginEnabledValue; this.loginEnabledValue = loginEnabledValue;
@ -121,183 +118,180 @@ public class SecurityConfiguration {
if (loginEnabledValue) { if (loginEnabledValue) {
http.addFilterBefore( http.addFilterBefore(
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
if (!applicationProperties.getSecurity().getCsrfDisabled()) { if (!applicationProperties.getSecurity().getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse(); CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = CsrfTokenRequestAttributeHandler requestHandler =
new CsrfTokenRequestAttributeHandler(); new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName(null); requestHandler.setCsrfRequestAttributeName(null);
http.csrf( http.csrf(
csrf -> csrf ->
csrf.ignoringRequestMatchers( csrf.ignoringRequestMatchers(
request -> { request -> {
String apiKey = request.getHeader("X-API-KEY"); String apiKey = request.getHeader("X-API-KEY");
// If there's no API key, don't ignore CSRF // If there's no API key, don't ignore CSRF
// (return false) // (return false)
if (apiKey == null || apiKey.trim().isEmpty()) { if (apiKey == null || apiKey.trim().isEmpty()) {
return false; return false;
} }
// Validate API key using existing UserService // Validate API key using existing UserService
try { try {
Optional<User> user = Optional<User> user =
userService.getUserByApiKey(apiKey); userService.getUserByApiKey(apiKey);
// If API key is valid, ignore CSRF (return // If API key is valid, ignore CSRF (return
// true) // true)
// If API key is invalid, don't ignore CSRF // If API key is invalid, don't ignore CSRF
// (return false) // (return false)
return user.isPresent(); return user.isPresent();
} catch (Exception e) { } catch (Exception e) {
// If there's any error validating the API // If there's any error validating the API
// key, don't ignore CSRF // key, don't ignore CSRF
return false; return false;
} }
}) })
.csrfTokenRepository(cookieRepo) .csrfTokenRepository(cookieRepo)
.csrfTokenRequestHandler(requestHandler)); .csrfTokenRequestHandler(requestHandler));
} }
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement( http.sessionManagement(
sessionManagement -> sessionManagement ->
sessionManagement sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(10) .maximumSessions(10)
.maxSessionsPreventsLogin(false) .maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry) .sessionRegistry(sessionRegistry)
.expiredUrl("/login?logout=true")); .expiredUrl("/login?logout=true"));
http.authenticationProvider(daoAuthenticationProvider()); http.authenticationProvider(daoAuthenticationProvider());
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
http.logout( http.logout(
logout -> logout ->
logout.logoutRequestMatcher( logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
PathPatternRequestMatcher.withDefaults() .logoutSuccessHandler(
.matcher("/logout")) new CustomLogoutSuccessHandler(applicationProperties, appConfig))
.logoutSuccessHandler( .clearAuthentication(true)
new CustomLogoutSuccessHandler( .invalidateHttpSession(true)
applicationProperties, appConfig)) .deleteCookies("JSESSIONID", "remember-me"));
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me"));
http.rememberMe( http.rememberMe(
rememberMeConfigurer -> // Use the configurator directly rememberMeConfigurer -> // Use the configurator directly
rememberMeConfigurer rememberMeConfigurer
.tokenRepository(persistentTokenRepository()) .tokenRepository(persistentTokenRepository())
.tokenValiditySeconds( // 14 days .tokenValiditySeconds( // 14 days
14 * 24 * 60 * 60) 14 * 24 * 60 * 60)
.userDetailsService( // Your existing UserDetailsService .userDetailsService( // Your existing UserDetailsService
userDetailsService) userDetailsService)
.useSecureCookie( // Enable secure cookie .useSecureCookie( // Enable secure cookie
true) true)
.rememberMeParameter( // Form parameter name .rememberMeParameter( // Form parameter name
"remember-me") "remember-me")
.rememberMeCookieName( // Cookie name .rememberMeCookieName( // Cookie name
"remember-me") "remember-me")
.alwaysRemember(false)); .alwaysRemember(false));
http.authorizeHttpRequests( http.authorizeHttpRequests(
authz -> authz ->
authz.requestMatchers( authz.requestMatchers(
req -> { req -> {
String uri = req.getRequestURI(); String uri = req.getRequestURI();
String contextPath = req.getContextPath(); String contextPath = req.getContextPath();
// Remove the context path from the URI // Remove the context path from the URI
String trimmedUri = String trimmedUri =
uri.startsWith(contextPath) uri.startsWith(contextPath)
? uri.substring( ? uri.substring(
contextPath.length()) contextPath.length())
: uri; : uri;
return trimmedUri.startsWith("/login") return trimmedUri.startsWith("/login")
|| trimmedUri.startsWith("/oauth") || trimmedUri.startsWith("/oauth")
|| trimmedUri.startsWith("/saml2") || trimmedUri.startsWith("/saml2")
|| trimmedUri.endsWith(".svg") || trimmedUri.endsWith(".svg")
|| trimmedUri.startsWith("/register") || trimmedUri.startsWith("/register")
|| trimmedUri.startsWith("/error") || trimmedUri.startsWith("/error")
|| trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/images/")
|| trimmedUri.startsWith("/public/") || trimmedUri.startsWith("/public/")
|| trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/css/")
|| trimmedUri.startsWith("/fonts/") || trimmedUri.startsWith("/fonts/")
|| trimmedUri.startsWith("/js/") || trimmedUri.startsWith("/js/")
|| trimmedUri.startsWith( || trimmedUri.startsWith(
"/api/v1/info/status"); "/api/v1/info/status");
}) })
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated()); .authenticated());
// Handle User/Password Logins // Handle User/Password Logins
if (applicationProperties.getSecurity().isUserPass()) { if (applicationProperties.getSecurity().isUserPass()) {
http.formLogin( http.formLogin(
formLogin -> formLogin ->
formLogin formLogin
.loginPage("/login") .loginPage("/login")
.successHandler( .successHandler(
new CustomAuthenticationSuccessHandler( new CustomAuthenticationSuccessHandler(
loginAttemptService, userService)) loginAttemptService, userService))
.failureHandler( .failureHandler(
new CustomAuthenticationFailureHandler( new CustomAuthenticationFailureHandler(
loginAttemptService, userService)) loginAttemptService, userService))
.defaultSuccessUrl("/") .defaultSuccessUrl("/")
.permitAll()); .permitAll());
} }
// Handle OAUTH2 Logins // Handle OAUTH2 Logins
if (applicationProperties.getSecurity().isOauth2Active()) { if (applicationProperties.getSecurity().isOauth2Active()) {
http.oauth2Login( http.oauth2Login(
oauth2 -> oauth2 ->
oauth2.loginPage("/oauth2") oauth2.loginPage("/oauth2")
/* /*
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser' If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser'
is set as true, else login fails with an error message advising the same. is set as true, else login fails with an error message advising the same.
*/ */
.successHandler( .successHandler(
new CustomOAuth2AuthenticationSuccessHandler( new CustomOAuth2AuthenticationSuccessHandler(
loginAttemptService, loginAttemptService,
applicationProperties, applicationProperties,
userService)) userService))
.failureHandler( .failureHandler(
new CustomOAuth2AuthenticationFailureHandler()) new CustomOAuth2AuthenticationFailureHandler())
. // Add existing Authorities from the database . // Add existing Authorities from the database
userInfoEndpoint( userInfoEndpoint(
userInfoEndpoint -> userInfoEndpoint ->
userInfoEndpoint userInfoEndpoint
.oidcUserService( .oidcUserService(
new CustomOAuth2UserService( new CustomOAuth2UserService(
applicationProperties, applicationProperties,
userService, userService,
loginAttemptService)) loginAttemptService))
.userAuthoritiesMapper( .userAuthoritiesMapper(
oAuth2userAuthoritiesMapper)) oAuth2userAuthoritiesMapper))
.permitAll()); .permitAll());
} }
// Handle SAML // Handle SAML
if (applicationProperties.getSecurity().isSaml2Active() && runningProOrHigher) { if (applicationProperties.getSecurity().isSaml2Active() && runningProOrHigher) {
// Configure the authentication provider // Configure the authentication provider
OpenSaml4AuthenticationProvider authenticationProvider = OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider(); new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter( authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService)); new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider) http.authenticationProvider(authenticationProvider)
.saml2Login( .saml2Login(
saml2 -> { saml2 -> {
try { try {
saml2.loginPage("/saml2") saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository( .relyingPartyRegistrationRepository(
saml2RelyingPartyRegistrations) saml2RelyingPartyRegistrations)
.authenticationManager( .authenticationManager(
new ProviderManager(authenticationProvider)) new ProviderManager(authenticationProvider))
.successHandler( .successHandler(
new CustomSaml2AuthenticationSuccessHandler( new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService, loginAttemptService,
applicationProperties, applicationProperties,
userService)) userService))
.failureHandler( .failureHandler(
new CustomSaml2AuthenticationFailureHandler()) new CustomSaml2AuthenticationFailureHandler())
.authenticationRequestResolver( .authenticationRequestResolver(
saml2AuthenticationRequestResolver); saml2AuthenticationRequestResolver);
} catch (Exception e) { } catch (Exception e) {
log.error("Error configuring SAML 2 login", e); log.error("Error configuring SAML 2 login", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
} }
} else { } else {
log.debug("Login is not enabled."); log.debug("Login is not enabled.");
@ -307,7 +301,8 @@ public class SecurityConfiguration {
} }
public DaoAuthenticationProvider daoAuthenticationProvider() { public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder()); provider.setPasswordEncoder(passwordEncoder());
return provider; return provider;
} }

View File

@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.database.H2SQLCondition; import stirling.software.proprietary.security.database.H2SQLCondition;
import stirling.software.proprietary.security.service.DatabaseService; import stirling.software.proprietary.security.service.DatabaseService;

View File

@ -29,11 +29,11 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.security.model.AuthenticationType; import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.common.model.enumeration.Role;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.security.model.api.user.UsernameAndPass; import stirling.software.proprietary.security.model.api.user.UsernameAndPass;
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@ -171,19 +171,16 @@ public class UserController {
* Updates the user settings based on the provided JSON payload. * Updates the user settings based on the provided JSON payload.
* *
* @param updates A map containing the settings to update. The expected structure is: * @param updates A map containing the settings to update. The expected structure is:
* <ul> * <ul>
* <li><b>emailNotifications</b> (optional): "true" or "false" - Enable or disable email * <li><b>emailNotifications</b> (optional): "true" or "false" - Enable or disable email notifications.</li>
* notifications. * <li><b>theme</b> (optional): "light" or "dark" - Set the user's preferred theme.</li>
* <li><b>theme</b> (optional): "light" or "dark" - Set the user's preferred theme. * <li><b>language</b> (optional): A string representing the preferred language (e.g., "en", "fr").</li>
* <li><b>language</b> (optional): A string representing the preferred language (e.g., * </ul>
* "en", "fr"). * Keys not listed above will be ignored.
* </ul>
* Keys not listed above will be ignored.
* @param principal The currently authenticated user. * @param principal The currently authenticated user.
* @return A redirect string to the account page after updating the settings. * @return A redirect string to the account page after updating the settings.
* @throws SQLException If a database error occurs. * @throws SQLException If a database error occurs.
* @throws UnsupportedProviderException If the operation is not supported for the user's * @throws UnsupportedProviderException If the operation is not supported for the user's provider.
* provider.
*/ */
public String updateUserSettings(@RequestBody Map<String, String> updates, Principal principal) public String updateUserSettings(@RequestBody Map<String, String> updates, Principal principal)
throws SQLException, UnsupportedProviderException { throws SQLException, UnsupportedProviderException {

View File

@ -1,6 +1,6 @@
package stirling.software.proprietary.security.controller.web; package stirling.software.proprietary.security.controller.web;
import static stirling.software.common.util.ProviderUtils.validateProvider; import static stirling.software.common.util.ProviderUtil.validateProvider;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
@ -29,15 +29,15 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security; import stirling.software.common.model.ApplicationProperties.Security;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.oauth2.provider.GitHubProvider;
import stirling.software.common.model.oauth2.GitHubProvider; import stirling.software.common.model.oauth2.provider.GoogleProvider;
import stirling.software.common.model.oauth2.GoogleProvider; import stirling.software.common.model.oauth2.provider.KeycloakProvider;
import stirling.software.common.model.oauth2.KeycloakProvider;
import stirling.software.proprietary.security.database.repository.UserRepository; import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.Authority; import stirling.software.proprietary.security.model.Authority;
import stirling.software.proprietary.security.model.SessionEntity; import stirling.software.proprietary.security.model.SessionEntity;

View File

@ -1,13 +1,10 @@
package stirling.software.proprietary.security.database; package stirling.software.proprietary.security.database;
import java.sql.SQLException; import java.sql.SQLException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.security.service.DatabaseServiceInterface; import stirling.software.proprietary.security.service.DatabaseServiceInterface;

View File

@ -4,7 +4,6 @@ import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.security.model.Authority; import stirling.software.proprietary.security.model.Authority;
@Repository @Repository

View File

@ -5,7 +5,6 @@ import java.util.Date;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import stirling.software.proprietary.security.model.PersistentLogin; import stirling.software.proprietary.security.model.PersistentLogin;
public class JPATokenRepositoryImpl implements PersistentTokenRepository { public class JPATokenRepositoryImpl implements PersistentTokenRepository {

View File

@ -2,7 +2,6 @@ package stirling.software.proprietary.security.database.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.security.model.PersistentLogin; import stirling.software.proprietary.security.model.PersistentLogin;
@Repository @Repository

View File

@ -1,16 +1,13 @@
package stirling.software.proprietary.security.database.repository; package stirling.software.proprietary.security.database.repository;
import jakarta.transaction.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import jakarta.transaction.Transactional;
import stirling.software.proprietary.security.model.SessionEntity; import stirling.software.proprietary.security.model.SessionEntity;
@Repository @Repository

View File

@ -7,7 +7,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
@Repository @Repository

View File

@ -1,25 +1,21 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import stirling.software.common.util.RequestUriUtils; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import stirling.software.common.util.RequestUriUtil;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@ -41,7 +37,7 @@ public class FirstLoginFilter extends OncePerRequestFilter {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
// Check if the request is for static resources // Check if the request is for static resources
boolean isStaticResource = RequestUriUtils.isStaticResource(contextPath, requestURI); boolean isStaticResource = RequestUriUtil.isStaticResource(contextPath, requestURI);
// If it's a static resource, just continue the filter chain and skip the logic below // If it's a static resource, just continue the filter chain and skip the logic below
if (isStaticResource) { if (isStaticResource) {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@ -1,19 +1,16 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import stirling.software.common.util.RequestUriUtil;
import stirling.software.common.util.RequestUriUtils;
@RequiredArgsConstructor @RequiredArgsConstructor
public class IPRateLimitingFilter implements Filter { public class IPRateLimitingFilter implements Filter {
@ -33,7 +30,7 @@ public class IPRateLimitingFilter implements Filter {
String requestURI = httpRequest.getRequestURI(); String requestURI = httpRequest.getRequestURI();
// Check if the request is for static resources // Check if the request is for static resources
boolean isStaticResource = boolean isStaticResource =
RequestUriUtils.isStaticResource(httpRequest.getContextPath(), requestURI); RequestUriUtil.isStaticResource(httpRequest.getContextPath(), requestURI);
// If it's a static resource, just continue the filter chain and skip the logic below // If it's a static resource, just continue the filter chain and skip the logic below
if (isStaticResource) { if (isStaticResource) {

View File

@ -1,9 +1,13 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -16,14 +20,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;

View File

@ -1,10 +1,17 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import io.github.pixee.security.Newlines;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -13,17 +20,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import io.github.pixee.security.Newlines;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
@Component @Component

View File

@ -14,7 +14,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
@Entity @Entity

View File

@ -7,7 +7,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.api.GeneralFile; import stirling.software.common.model.api.GeneralFile;
@Data @Data

View File

@ -1,7 +1,10 @@
package stirling.software.proprietary.security.oauth2; package stirling.software.proprietary.security.oauth2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
@ -10,12 +13,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class CustomOAuth2AuthenticationFailureHandler public class CustomOAuth2AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler { extends SimpleUrlAuthenticationFailureHandler {

View File

@ -1,26 +1,22 @@
package stirling.software.proprietary.security.oauth2; package stirling.software.proprietary.security.oauth2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.security.web.savedrequest.SavedRequest;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.common.util.RequestUriUtils; import stirling.software.common.util.RequestUriUtil;
import stirling.software.proprietary.security.model.AuthenticationType; import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@ -56,7 +52,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
: null; : null;
if (savedRequest != null if (savedRequest != null
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) { && !RequestUriUtil.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
// Redirect to the original destination // Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication); super.onAuthenticationSuccess(request, response, authentication);
} else { } else {

View File

@ -1,16 +1,12 @@
package stirling.software.proprietary.security.oauth2; package stirling.software.proprietary.security.oauth2;
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
import static stirling.software.common.util.ProviderUtils.validateProvider;
import static stirling.software.common.util.ValidationUtils.isStringEmpty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -23,24 +19,25 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.ClientRegistrations; import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.oauth2.GitHubProvider;
import stirling.software.common.model.oauth2.GoogleProvider;
import stirling.software.common.model.oauth2.KeycloakProvider;
import stirling.software.common.model.oauth2.Provider;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.model.exception.NoProviderFoundException; import stirling.software.proprietary.security.model.exception.NoProviderFoundException;
import stirling.software.common.model.oauth2.provider.GitHubProvider;
import stirling.software.common.model.oauth2.provider.GoogleProvider;
import stirling.software.common.model.oauth2.provider.KeycloakProvider;
import stirling.software.common.model.oauth2.provider.Provider;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
import static stirling.software.common.util.ProviderUtil.validateProvider;
import static stirling.software.common.util.ValidationUtil.isStringEmpty;
@Slf4j @Slf4j
@Configuration @Configuration
@ConditionalOnBooleanProperty("security.oauth2.enabled") @ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
@ConditionalOnExpression("${docker.enable.security:true}")
public class OAuth2Configuration { public class OAuth2Configuration {
public static final String REDIRECT_URI_PATH = "{baseUrl}/login/oauth2/code/"; public static final String REDIRECT_URI_PATH = "{baseUrl}/login/oauth2/code/";
@ -55,6 +52,7 @@ public class OAuth2Configuration {
} }
@Bean @Bean
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
public ClientRegistrationRepository clientRegistrationRepository() public ClientRegistrationRepository clientRegistrationRepository()
throws NoProviderFoundException { throws NoProviderFoundException {
List<ClientRegistration> registrations = new ArrayList<>(); List<ClientRegistration> registrations = new ArrayList<>();

View File

@ -6,7 +6,6 @@ import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PEMParser;

View File

@ -3,7 +3,6 @@ package stirling.software.proprietary.security.saml2;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;

View File

@ -1,7 +1,9 @@
package stirling.software.proprietary.security.saml2; package stirling.software.proprietary.security.saml2;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
@ -9,11 +11,6 @@ import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

View File

@ -1,25 +1,21 @@
package stirling.software.proprietary.security.saml2; package stirling.software.proprietary.security.saml2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.SQLException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.common.util.RequestUriUtils; import stirling.software.common.util.RequestUriUtil;
import stirling.software.proprietary.security.model.AuthenticationType; import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@ -58,7 +54,7 @@ public class CustomSaml2AuthenticationSuccessHandler
savedRequest != null); savedRequest != null);
if (savedRequest != null if (savedRequest != null
&& !RequestUriUtils.isStaticResource( && !RequestUriUtil.isStaticResource(
contextPath, savedRequest.getRedirectUrl())) { contextPath, savedRequest.getRedirectUrl())) {
log.debug( log.debug(
"Valid saved request found, redirecting to original destination: {}", "Valid saved request found, redirecting to original destination: {}",

View File

@ -5,7 +5,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.Attribute;
@ -16,10 +17,6 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,9 +1,11 @@
package stirling.software.proprietary.security.saml2; package stirling.software.proprietary.security.saml2;
import jakarta.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collections; import java.util.Collections;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -18,12 +20,6 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;

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