Compare commits

..

1 Commits

Author SHA1 Message Date
stirlingbot[bot]
02f704db73
📁 pre-commit
Signed-off-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-05-26 00:16:13 +00:00
1099 changed files with 4810 additions and 27184 deletions

14
.gitattributes vendored
View File

@ -1,10 +1,10 @@
* text=auto eol=lf
# Ignore all JavaScript files in a directory
stirling-pdf/src/main/resources/static/pdfjs/* linguist-vendored
stirling-pdf/src/main/resources/static/pdfjs/** linguist-vendored
stirling-pdf/src/main/resources/static/pdfjs-legacy/* linguist-vendored
stirling-pdf/src/main/resources/static/pdfjs-legacy/** linguist-vendored
stirling-pdf/src/main/resources/static/css/bootstrap-icons.css linguist-vendored
stirling-pdf/src/main/resources/static/css/bootstrap.min.css linguist-vendored
stirling-pdf/src/main/resources/static/css/fonts/* linguist-vendored
src/main/resources/static/pdfjs/* linguist-vendored
src/main/resources/static/pdfjs/** linguist-vendored
src/main/resources/static/pdfjs-legacy/* linguist-vendored
src/main/resources/static/pdfjs-legacy/** linguist-vendored
src/main/resources/static/css/bootstrap-icons.css linguist-vendored
src/main/resources/static/css/bootstrap.min.css linguist-vendored
src/main/resources/static/css/fonts/* linguist-vendored

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

@ -1,45 +1,60 @@
Translation:
- changed-files:
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/messages_*_*.properties'
- any-glob-to-any-file: 'src/main/resources/messages_*_*.properties'
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/fragments/languages.html'
- any-glob-to-any-file: 'src/main/resources/templates/fragments/languages.html'
Front End:
- changed-files:
- 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/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: 'src/main/resources/templates/**/*'
- any-glob-to-any-file: 'src/main/resources/static/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/UI/**/*'
Java:
- changed-files:
- any-glob-to-any-file: 'common/src/main/java/**/*.java'
- any-glob-to-any-file: 'proprietary/src/main/java/**/*.java'
- any-glob-to-any-file: 'stirling-pdf/src/main/java/**/*.java'
- any-glob-to-any-file: 'src/main/java/**/*.java'
Back End:
- 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/controller/**/*'
- 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/banner.txt'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/**/*'
- any-glob-to-any-file: 'src/main/resources/settings.yml.template'
- any-glob-to-any-file: 'src/main/resources/application.properties'
- any-glob-to-any-file: 'src/main/resources/banner.txt'
- any-glob-to-any-file: 'scripts/png_to_webp.py'
- any-glob-to-any-file: 'split_photos.py'
Security:
- changed-files:
- any-glob-to-any-file: 'proprietary/src/main/java/stirling/software/proprietary/security/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/EmailController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/H2SQLController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/UserController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/Email.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/BackupNotFoundException.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundExceptionjava'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AttemptCounter.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/Authority.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/PersistentLogin.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/SessionEntity.java'
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
API:
- 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/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/model/api/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/**/*'
- any-glob-to-any-file: 'scripts/png_to_webp.py'
- any-glob-to-any-file: 'split_photos.py'
- any-glob-to-any-file: '.github/workflows/swagger.yml'
@ -73,9 +88,7 @@ Devtools:
Test:
- changed-files:
- any-glob-to-any-file: 'cucumber/**/*'
- any-glob-to-any-file: 'common/src/test/**/*'
- any-glob-to-any-file: 'proprietary/src/test/**/*'
- any-glob-to-any-file: 'stirling-pdf/src/test/**/*'
- any-glob-to-any-file: 'src/test/**/*'
- any-glob-to-any-file: 'src/testing/**/*'
- any-glob-to-any-file: '.pre-commit-config'
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'

View File

@ -196,9 +196,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
if len(file_list) == 1:
file_arr = file_list[0].split()
base_dir = os.path.abspath(
os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")
)
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
for file_path in file_arr:
file_normpath = os.path.normpath(file_path)
@ -218,19 +216,10 @@ def check_for_differences(reference_file, file_list, branch, actor):
or (
# only local windows command
not file_normpath.startswith(
os.path.join(
"", "stirling-pdf", "src", "main", "resources", "messages_"
)
os.path.join("", "src", "main", "resources", "messages_")
)
and not file_normpath.startswith(
os.path.join(
os.getcwd(),
"stirling-pdf",
"src",
"main",
"resources",
"messages_",
)
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
)
)
or not file_normpath.endswith(".properties")
@ -328,7 +317,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
report.append("## ❌ Overall Check Status: **_Failed_**")
report.append("")
report.append(
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/messages_en_GB.properties)"
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)"
)
else:
report.append("## ✅ Overall Check Status: **_Success_**")
@ -388,12 +377,7 @@ if __name__ == "__main__":
else:
file_list = glob.glob(
os.path.join(
os.getcwd(),
"stirling-pdf",
"src",
"main",
"resources",
"messages_*.properties",
os.getcwd(), "src", "main", "resources", "messages_*.properties"
)
)
update_missing_keys(args.reference_file, file_list)

View File

@ -37,7 +37,7 @@ jobs:
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
comment_id: ${{ github.event.comment.id }}
disable_security: ${{ steps.check-security-flag.outputs.disable_security }}
enable_security: ${{ steps.check-security-flag.outputs.enable_security }}
steps:
- name: Harden Runner
@ -84,7 +84,7 @@ jobs:
core.setOutput('repository', repository);
core.setOutput('ref', pr.head.ref);
- name: Check for security/login flag
id: check-security-flag
env:
@ -92,10 +92,10 @@ jobs:
run: |
if [[ "$COMMENT_BODY" == *"security"* ]] || [[ "$COMMENT_BODY" == *"login"* ]]; then
echo "Security flags detected in comment"
echo "disable_security=false" >> $GITHUB_OUTPUT
echo "enable_security=true" >> $GITHUB_OUTPUT
else
echo "No security flags detected in comment"
echo "disable_security=true" >> $GITHUB_OUTPUT
echo "enable_security=false" >> $GITHUB_OUTPUT
fi
- name: Add 'in_progress' reaction to comment
@ -155,10 +155,10 @@ jobs:
- name: Run Gradle Command
run: |
if [ "${{ needs.check-comment.outputs.disable_security }}" == "true" ]; then
export DISABLE_ADDITIONAL_FEATURES=true
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
export DOCKER_ENABLE_SECURITY=true
else
export DISABLE_ADDITIONAL_FEATURES=false
export DOCKER_ENABLE_SECURITY=false
fi
./gradlew clean build
env:
@ -180,7 +180,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }}
- 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:
context: .
file: ./Dockerfile
@ -199,7 +199,7 @@ jobs:
id: deploy
run: |
# Set security settings based on flags
if [ "${{ needs.check-comment.outputs.disable_security }}" == "false" ]; then
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
DOCKER_SECURITY="true"
LOGIN_SECURITY="true"
SECURITY_STATUS="🔒 Security Enabled"
@ -223,7 +223,7 @@ jobs:
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "${DOCKER_SECURITY}"
DOCKER_ENABLE_SECURITY: "${DOCKER_SECURITY}"
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
@ -250,7 +250,7 @@ jobs:
docker-compose pull
docker-compose up -d
ENDSSH
# Set output for use in PR comment
echo "security_status=${SECURITY_STATUS}" >> $GITHUB_ENV

View File

@ -40,63 +40,22 @@ jobs:
- name: Build with Gradle and no spring security
run: ./gradlew clean build
env:
DISABLE_ADDITIONAL_FEATURES: true
DOCKER_ENABLE_SECURITY: false
- name: Build with Gradle and with spring security
run: ./gradlew clean build
env:
DISABLE_ADDITIONAL_FEATURES: false
- name: Check Test Reports Exist
id: check-reports
if: always()
run: |
missing_reports=()
# Check for required test report directories
if [ ! -d "stirling-pdf/build/reports/tests/" ]; then
missing_reports+=("stirling-pdf/build/reports/tests/")
fi
if [ ! -d "stirling-pdf/build/test-results/" ]; then
missing_reports+=("stirling-pdf/build/test-results/")
fi
if [ ! -d "common/build/reports/tests/" ]; then
missing_reports+=("common/build/reports/tests/")
fi
if [ ! -d "common/build/test-results/" ]; then
missing_reports+=("common/build/test-results/")
fi
if [ ! -d "proprietary/build/reports/tests/" ]; then
missing_reports+=("proprietary/build/reports/tests/")
fi
if [ ! -d "proprietary/build/test-results/" ]; then
missing_reports+=("proprietary/build/test-results/")
fi
# Fail if any required reports are missing
if [ ${#missing_reports[@]} -gt 0 ]; then
echo "ERROR: The following required test report directories are missing:"
printf '%s\n' "${missing_reports[@]}"
exit 1
fi
echo "All required test report directories are present"
DOCKER_ENABLE_SECURITY: true
- name: Upload Test Reports
if: steps.check-reports.outcome == 'success'
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-reports-jdk-${{ matrix.jdk-version }}
path: |
stirling-pdf/build/reports/tests/
stirling-pdf/build/test-results/
stirling-pdf/build/reports/problems/
common/build/reports/tests/
common/build/test-results/
common/build/reports/problems/
proprietary/build/reports/tests/
proprietary/build/test-results/
proprietary/build/reports/problems/
build/reports/tests/
build/test-results/
build/reports/problems/
retention-days: 3
check-licence:

View File

@ -4,7 +4,7 @@ on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- "stirling-pdf/src/main/resources/messages_*.properties"
- "src/main/resources/messages_*.properties"
permissions:
contents: read # Allow read access to repository content
@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
issues: write # Allow posting comments on issues/PRs
pull-requests: write # Allow writing to pull requests
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
@ -25,18 +25,15 @@ jobs:
- name: Checkout main branch first
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
python-version: "3.12"
- name: Get PR data
id: get-pr-data
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const prNumber = context.payload.pull_request.number;
const repoOwner = context.payload.repository.owner.login;
@ -57,30 +54,16 @@ jobs:
- name: Fetch PR changed files
id: fetch-pr-changes
env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Fetching PR changed files..."
echo "Getting list of changed files from PR..."
# Check if PR number exists
if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then
echo "Error: PR number is empty"
exit 1
fi
# Get changed files and filter for properties files, handle case where no matches are found
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^stirling-pdf/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt || echo "No matching properties files found in PR"
# Check if any files were found
if [ ! -s changed_files.txt ]; then
echo "No properties files changed in this PR"
echo "Workflow will exit early as no relevant files to check"
exit 0
fi
echo "Found $(wc -l < changed_files.txt) matching properties files"
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt # Filter only matching property files
- name: Determine reference file test
id: determine-file
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const fs = require("fs");
const path = require("path");
@ -116,7 +99,7 @@ jobs:
// Filter for relevant files based on the PR changes
const changedFiles = files
.map(file => file.filename)
.filter(file => /^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
console.log("Changed files:", changedFiles);
@ -154,12 +137,12 @@ jobs:
// Determine reference file
let referenceFilePath;
if (changedFiles.includes("stirling-pdf/src/main/resources/messages_en_GB.properties")) {
if (changedFiles.includes("src/main/resources/messages_en_GB.properties")) {
console.log("Using PR branch reference file.");
const { data: fileContent } = await github.rest.repos.getContent({
owner: prRepoOwner,
repo: prRepoName,
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
path: "src/main/resources/messages_en_GB.properties",
ref: branch,
});
@ -171,7 +154,7 @@ jobs:
const { data: fileContent } = await github.rest.repos.getContent({
owner: repoOwner,
repo: repoName,
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
path: "src/main/resources/messages_en_GB.properties",
ref: "main",
});
@ -221,7 +204,6 @@ jobs:
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
@ -237,7 +219,7 @@ jobs:
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
// 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) {
// Update existing comment

View File

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

View File

@ -48,11 +48,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
disable_security: [true, false]
enable_security: [true, false]
include:
- disable_security: false
- enable_security: true
file_suffix: "-with-login"
- disable_security: true
- enable_security: false
file_suffix: ""
steps:
- name: Harden Runner
@ -72,10 +72,10 @@ jobs:
with:
gradle-version: 8.14
- name: Generate jar (Disable Security=${{ matrix.disable_security }})
- name: Generate jar (With Security=${{ matrix.enable_security }})
run: ./gradlew clean createExe
env:
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }}
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
STIRLING_PDF_DESKTOP_UI: false
- name: Rename binaries
@ -98,11 +98,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
disable_security: [true, false]
enable_security: [true, false]
include:
- disable_security: false
- enable_security: true
file_suffix: "with-login-"
- disable_security: true
- enable_security: false
file_suffix: ""
steps:
- name: Harden Runner
@ -171,7 +171,7 @@ jobs:
- name: Build Installer
run: ./gradlew build jpackage -x test --info
env:
DISABLE_ADDITIONAL_FEATURES: true
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
BROWSER_OPEN: true

View File

@ -20,49 +20,58 @@ jobs:
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
- 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: 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
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.12
cache: 'pip' # caching pip dependencies
- name: Run Pre-Commit Hooks
run: |
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
- run: pre-commit run --all-files -c .pre-commit-config.yaml
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
run: |
git add .
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ steps.setup-bot.outputs.token }}
token: ${{ steps.generate-token.outputs.token }}
commit-message: ":file_folder: pre-commit"
committer: ${{ steps.setup-bot.outputs.committer }}
author: ${{ steps.setup-bot.outputs.committer }}
committer: ${{ steps.committer.outputs.string }}
author: ${{ steps.committer.outputs.string }}
signoff: true
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: |
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
draft: false

View File

@ -37,7 +37,7 @@ jobs:
- name: Run Gradle Command
run: ./gradlew clean build
env:
DISABLE_ADDITIONAL_FEATURES: true
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: false
- name: Install cosign
@ -90,7 +90,7 @@ jobs:
- name: Build and push main Dockerfile
id: build-push-regular
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
@ -135,7 +135,7 @@ jobs:
- name: Build and push Dockerfile-ultra-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'
with:
context: .
@ -166,7 +166,7 @@ jobs:
- name: Build and push main Dockerfile 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'
with:
builder: ${{ steps.buildx.outputs.name }}

View File

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

View File

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

View File

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

View File

@ -8,15 +8,52 @@ on:
paths:
- "build.gradle"
- "README.md"
- "stirling-pdf/src/main/resources/messages_*.properties"
- "stirling-pdf/src/main/resources/static/3rdPartyLicenses.json"
- "src/main/resources/messages_*.properties"
- "src/main/resources/static/3rdPartyLicenses.json"
- "scripts/ignore_translation.toml"
permissions:
contents: read
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:
needs: ["read_bot_entries"]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
@ -24,29 +61,34 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
- 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 }}
app-id: ${{ vars.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: "pip" # caching pip dependencies
cache: 'pip' # caching pip dependencies
- name: Sync translation property files
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 "src/main/resources/messages_en_GB.properties" --branch main
- name: Commit translation files
- name: Set up git config
run: |
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 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: |
git add src/main/resources/messages_*.properties
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
- name: Install dependencies
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
@ -58,16 +100,15 @@ jobs:
- name: Run git add
run: |
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
if: always()
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ steps.setup-bot.outputs.token }}
token: ${{ steps.generate-token.outputs.token }}
commit-message: Update files
committer: ${{ steps.setup-bot.outputs.committer }}
author: ${{ steps.setup-bot.outputs.committer }}
committer: ${{ needs.read_bot_entries.outputs.committer }}
author: ${{ needs.read_bot_entries.outputs.committer }}
signoff: true
branch: sync_readme
title: ":globe_with_meridians: Sync Translations + Update README Progress Table"
@ -101,4 +142,4 @@ jobs:
sign-commits: true
add-paths: |
README.md
stirling-pdf/src/main/resources/messages_*.properties
src/main/resources/messages_*.properties

View File

@ -28,7 +28,7 @@ jobs:
- name: Build with Gradle
run: ./gradlew clean build
env:
DISABLE_ADDITIONAL_FEATURES: true
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
@ -46,7 +46,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }}
- 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:
context: .
file: ./Dockerfile
@ -76,7 +76,7 @@ jobs:
- /stirling/test-${{ github.sha }}/config:/configs:rw
- /stirling/test-${{ github.sha }}/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF Test"

5
.gitignore vendored
View File

@ -13,7 +13,6 @@ local.properties
.recommenders
.classpath
.project
*.local.json
version.properties
#### Stirling-PDF Files ###
@ -125,9 +124,6 @@ SwaggerDoc.json
*.rar
*.db
/build
/stirling-pdf/build
/common/build
/proprietary/build
# Byte-compiled / optimized / DLL files
__pycache__/
@ -197,3 +193,4 @@ id_ed25519.pub
# node_modules
node_modules/
*.mjs

View File

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

View File

@ -10,7 +10,7 @@
"java.configuration.updateBuildConfiguration": "interactive",
"java.format.enabled": true,
"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",
// (DE) Aktiviert Kommentare im Java-Format.
// (EN) Enables comments in Java formatting.
@ -49,11 +49,7 @@
".venv*/",
".vscode/",
"bin/",
"common/bin/",
"proprietary/bin/",
"build/",
"common/build/",
"proprietary/build/",
"configs/",
"customFiles/",
"docs/",
@ -67,8 +63,6 @@
".git-blame-ignore-revs",
".gitattributes",
".gitignore",
"common/.gitignore",
"proprietary/.gitignore",
".pre-commit-config.yaml",
],
// Enables signature help in Java.

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.
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 DISABLE_ADDITIONAL_FEATURES=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. To do this, you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step.
## 4. Project Structure
@ -114,9 +114,9 @@ Stirling-PDF offers several Docker versions:
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
- `docker-compose-latest.yml`: Latest version without login and security features
- `docker-compose-latest-security.yml`: Latest version with login and security features enabled
- `docker-compose-latest-fat-security.yml`: Fat version with login and security features enabled
- `docker-compose-latest.yml`: Latest version without security features
- `docker-compose-latest-security.yml`: Latest version with security features enabled
- `docker-compose-latest-fat-security.yml`: Fat version with security features enabled
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
@ -137,11 +137,11 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "false"
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
PUID: 1002
PGID: 1002
@ -170,7 +170,7 @@ Stirling-PDF uses different Docker images for various configurations. The build
1. Set the security environment variable:
```bash
export DISABLE_ADDITIONAL_FEATURES=true # or false for to enable login and security features for builds
export DOCKER_ENABLE_SECURITY=false # or true for security-enabled builds
```
2. Build the project with Gradle:
@ -193,10 +193,10 @@ Stirling-PDF uses different Docker images for various configurations. The build
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
```
For the fat version (with login and security features enabled):
For the fat version (with security enabled):
```bash
export DISABLE_ADDITIONAL_FEATURES=false
export DOCKER_ENABLE_SECURITY=true
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
```
@ -332,7 +332,7 @@ Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF
### Thymeleaf overview
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `stirling-pdf/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
Some examples of this are:
@ -384,7 +384,7 @@ This would generate n entries of tr for each person in exampleData
### Adding a New Feature to the Backend (API)
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 `src/main/java/stirling/software/SPDF/controller/api` directory.
- 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")`.
@ -411,7 +411,7 @@ This would generate n entries of tr for each person in exampleData
```
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 `src/main/java/stirling/software/SPDF/service` directory.
- Implement the business logic for the new feature.
```java
@ -463,7 +463,7 @@ This would generate n entries of tr for each person in exampleData
### Adding a New Feature to the Frontend (UI)
1. **Create a New Thymeleaf Template:**
- Create a new HTML file in the `stirling-pdf/src/main/resources/templates` directory.
- Create a new HTML file in the `src/main/resources/templates` directory.
- Use Thymeleaf attributes to dynamically generate content.
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
@ -507,7 +507,7 @@ This would generate n entries of tr for each person in exampleData
```
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 `src/main/java/stirling/software/SPDF/controller/ui` directory.
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
```java
@ -537,7 +537,7 @@ This would generate n entries of tr for each person in exampleData
3. **Update the Navigation Bar:**
- Add a link to the new feature page in the navigation bar.
- Update the `stirling-pdf/src/main/resources/templates/fragments/navbar.html` file.
- Update the `src/main/resources/templates/fragments/navbar.html` file.
```html
<li class="nav-item">
@ -551,7 +551,7 @@ When adding a new feature or modifying existing ones in Stirling-PDF, you'll nee
### 1. Locate Existing Language Files
Find the existing `messages.properties` files in the `stirling-pdf/src/main/resources` directory. You'll see files like:
Find the existing `messages.properties` files in the `src/main/resources` directory. You'll see files like:
- `messages.properties` (default, usually English)
- `messages_en_GB.properties`

View File

@ -1,11 +1,12 @@
# Main stage
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# Copy necessary files
COPY scripts /scripts
COPY pipeline /pipeline
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY stirling-pdf/build/libs/*.jar app.jar
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
#COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
COPY build/libs/*.jar app.jar
ARG VERSION_TAG
@ -22,7 +23,7 @@ LABEL org.opencontainers.image.version="${VERSION_TAG}"
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
# Set Environment Variables
ENV DISABLE_ADDITIONAL_FEATURES=true \
ENV DOCKER_ENABLE_SECURITY=false \
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_CUSTOM_OPTS="" \
@ -72,7 +73,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
py3-pillow@testing \
py3-pdf2image@testing && \
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 && \
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/ && \

View File

@ -32,7 +32,6 @@ ENV SETUPTOOLS_USE_DISTUTILS=local
# Installation der benötigten Python-Pakete
RUN python3 -m venv --system-site-packages /opt/venv \
&& . /opt/venv/bin/activate \
&& pip install --upgrade pip setuptools \
&& 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

View File

@ -5,9 +5,6 @@ COPY build.gradle .
COPY settings.gradle .
COPY gradlew .
COPY gradle gradle/
COPY stirling-pdf/build.gradle stirling-pdf/.
COPY common/build.gradle common/.
COPY proprietary/build.gradle proprietary/.
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
# Set the working directory
@ -16,24 +13,24 @@ WORKDIR /app
# Copy the entire project to the working directory
COPY . .
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
RUN DISABLE_ADDITIONAL_FEATURES=false \
# Build the application with DOCKER_ENABLE_SECURITY=false
RUN DOCKER_ENABLE_SECURITY=true \
STIRLING_PDF_DESKTOP_UI=false \
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
# Main stage
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# Copy necessary files
COPY scripts /scripts
COPY pipeline /pipeline
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY --from=build /app/stirling-pdf/build/libs/*.jar app.jar
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY --from=build /app/build/libs/*.jar app.jar
ARG VERSION_TAG
# Set Environment Variables
ENV DISABLE_ADDITIONAL_FEATURES=true \
ENV DOCKER_ENABLE_SECURITY=false \
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_CUSTOM_OPTS="" \
@ -86,7 +83,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
py3-pillow@testing \
py3-pdf2image@testing && \
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 && \
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/ && \

View File

@ -1,10 +1,10 @@
# use alpine
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
ARG VERSION_TAG
# Set Environment Variables
ENV DISABLE_ADDITIONAL_FEATURES=true \
ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \
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" \
@ -18,7 +18,7 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY scripts/installFonts.sh /scripts/installFonts.sh
COPY pipeline /pipeline
COPY stirling-pdf/build/libs/*.jar app.jar
COPY build/libs/*.jar app.jar
# Set up necessary directories and permissions
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \

View File

@ -10,7 +10,7 @@ Fork Stirling-PDF and create a new branch out of `main`.
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/templates/fragments/languages.html)
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
For example, to add Polish, you would add:
@ -25,7 +25,7 @@ The `data-bs-language-code` is the code used to reference the file in the next s
Start by copying the existing English property file:
- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/messages_en_GB.properties)
- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
Copy and rename it to `messages_{your data-bs-language-code here}.properties`. In the Polish example, you would set the name to `messages_pl_PL.properties`.

View File

@ -1,13 +1,6 @@
MIT License
Copyright (c) 2025 Stirling PDF Inc.
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".
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
Copyright (c) 2024 Stirling Tools
Permission is hereby granted, free of charge, to any person obtaining a copy
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 |
| -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![68%](https://geps.dev/progress/68) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![69%](https://geps.dev/progress/69) |
| Basque (Euskara) (eu_ES) | ![40%](https://geps.dev/progress/40) |
| Bulgarian (Български) (bg_BG) | ![76%](https://geps.dev/progress/76) |
| Catalan (Català) (ca_CA) | ![75%](https://geps.dev/progress/75) |
| Croatian (Hrvatski) (hr_HR) | ![67%](https://geps.dev/progress/67) |
| Czech (Česky) (cs_CZ) | ![78%](https://geps.dev/progress/78) |
| Danish (Dansk) (da_DK) | ![69%](https://geps.dev/progress/69) |
| Dutch (Nederlands) (nl_NL) | ![67%](https://geps.dev/progress/67) |
| Arabic (العربية) (ar_AR) | ![83%](https://geps.dev/progress/83) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![82%](https://geps.dev/progress/82) |
| Basque (Euskara) (eu_ES) | ![48%](https://geps.dev/progress/48) |
| Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) |
| Catalan (Català) (ca_CA) | ![89%](https://geps.dev/progress/89) |
| Croatian (Hrvatski) (hr_HR) | ![81%](https://geps.dev/progress/81) |
| Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) |
| Danish (Dansk) (da_DK) | ![80%](https://geps.dev/progress/80) |
| Dutch (Nederlands) (nl_NL) | ![79%](https://geps.dev/progress/79) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![77%](https://geps.dev/progress/77) |
| German (Deutsch) (de_DE) | ![93%](https://geps.dev/progress/93) |
| Greek (Ελληνικά) (el_GR) | ![75%](https://geps.dev/progress/75) |
| Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) |
| Hungarian (Magyar) (hu_HU) | ![95%](https://geps.dev/progress/95) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![69%](https://geps.dev/progress/69) |
| Irish (Gaeilge) (ga_IE) | ![76%](https://geps.dev/progress/76) |
| Italian (Italiano) (it_IT) | ![87%](https://geps.dev/progress/87) |
| Japanese (日本語) (ja_JP) | ![76%](https://geps.dev/progress/76) |
| Korean (한국어) (ko_KR) | ![75%](https://geps.dev/progress/75) |
| Norwegian (Norsk) (no_NB) | ![73%](https://geps.dev/progress/73) |
| Persian (فارسی) (fa_IR) | ![72%](https://geps.dev/progress/72) |
| Polish (Polski) (pl_PL) | ![80%](https://geps.dev/progress/80) |
| Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) |
| Portuguese Brazilian (Português) (pt_BR) | ![84%](https://geps.dev/progress/84) |
| Romanian (Română) (ro_RO) | ![64%](https://geps.dev/progress/64) |
| Russian (Русский) (ru_RU) | ![76%](https://geps.dev/progress/76) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![49%](https://geps.dev/progress/49) |
| Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) |
| Slovakian (Slovensky) (sk_SK) | ![57%](https://geps.dev/progress/57) |
| Slovenian (Slovenščina) (sl_SI) | ![79%](https://geps.dev/progress/79) |
| Spanish (Español) (es_ES) | ![82%](https://geps.dev/progress/82) |
| Swedish (Svenska) (sv_SE) | ![73%](https://geps.dev/progress/73) |
| Thai (ไทย) (th_TH) | ![66%](https://geps.dev/progress/66) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![0%](https://geps.dev/progress/0) |
| Traditional Chinese (繁體中文) (zh_TW) | ![84%](https://geps.dev/progress/84) |
| Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) |
| Ukrainian (Українська) (uk_UA) | ![79%](https://geps.dev/progress/79) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![64%](https://geps.dev/progress/64) |
| Malayalam (മലയാളം) (ml_IN) | ![0%](https://geps.dev/progress/0) |
| French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) |
| Hindi (हिंदी) (hi_IN) | ![91%](https://geps.dev/progress/91) |
| Hungarian (Magyar) (hu_HU) | ![99%](https://geps.dev/progress/99) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![80%](https://geps.dev/progress/80) |
| Irish (Gaeilge) (ga_IE) | ![91%](https://geps.dev/progress/91) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) |
| Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) |
| Persian (فارسی) (fa_IR) | ![87%](https://geps.dev/progress/87) |
| Polish (Polski) (pl_PL) | ![95%](https://geps.dev/progress/95) |
| Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) |
| Romanian (Română) (ro_RO) | ![75%](https://geps.dev/progress/75) |
| Russian (Русский) (ru_RU) | ![93%](https://geps.dev/progress/93) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![60%](https://geps.dev/progress/60) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![69%](https://geps.dev/progress/69) |
| Slovenian (Slovenščina) (sl_SI) | ![94%](https://geps.dev/progress/94) |
| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) |
| Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) |
| Thai (ไทย) (th_TH) | ![80%](https://geps.dev/progress/80) |
| Tibetan (བོད་ཡིག་) (zh_BO) | ![88%](https://geps.dev/progress/88) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) |
| Malayalam (മലയാളം) (ml_ML) | ![99%](https://geps.dev/progress/99) |
## Stirling PDF Enterprise

View File

@ -1,15 +1,15 @@
plugins {
id "java"
id "jacoco"
id 'jacoco'
id "org.springframework.boot" version "3.4.5"
id "io.spring.dependency-management" version "1.1.7"
id "org.springframework.boot" version "3.5.0"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2"
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 "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"
}
@ -19,136 +19,28 @@ import java.nio.file.Files
import java.time.Year
ext {
springBootVersion = "3.5.0"
springBootVersion = "3.4.5"
pdfboxVersion = "3.0.5"
imageioVersion = "3.12.0"
lombokVersion = "1.18.38"
bouncycastleVersion = "1.80"
springSecuritySamlVersion = "6.5.0"
openSamlVersion = "4.3.2"
commonmarkVersion = "0.24.0"
tempJrePath = null
}
jar {
enabled = false
manifest {
attributes "Implementation-Title": "Stirling-PDF",
"Implementation-Version": project.version
}
group = "stirling.software"
version = "0.46.2"
java {
// 17 is lowest but we support and recommend 21
sourceCompatibility = JavaVersion.VERSION_17
}
bootJar {
enabled = false
}
sourceSets {
main {
java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == '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('DISABLE_ADDITIONAL_FEATURES') == 'true'
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
exclude 'stirling/software/proprietary/security/**'
}
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**'
}
}
}
}
allprojects {
group = 'stirling.software'
version = '1.0.0'
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'com.diffplug.spotless'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
java {
// 17 is lowest but we support and recommend 21
sourceCompatibility = JavaVersion.VERSION_17
}
bootJar {
enabled = false
}
repositories {
mavenCentral()
}
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
// Exclude vulnerable BouncyCastle version used in tableau
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
}
dependencyManagement {
imports {
mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersion"
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.github.pixee:java-security-toolkit:1.2.1'
//tmp for security bumps
implementation 'ch.qos.logback:logback-core:1.5.18'
implementation 'ch.qos.logback:logback-classic:1.5.18'
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
dependsOn "spotlessApply"
}
compileJava {
options.compilerArgs << "-parameters"
}
test {
useJUnitPlatform()
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
dependsOn "spotlessApply"
repositories {
mavenCentral()
maven { url = "https://build.shibboleth.net/maven/releases" }
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
}
licenseReport {
@ -159,14 +51,29 @@ licenseReport {
sourceSets {
main {
java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
exclude 'stirling/software/proprietary/security/**'
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
exclude "stirling/software/SPDF/config/interfaces/DatabaseInterface.java"
exclude "stirling/software/SPDF/config/security/**"
exclude "stirling/software/SPDF/controller/api/DatabaseController.java"
exclude "stirling/software/SPDF/controller/api/EmailController.java"
exclude "stirling/software/SPDF/controller/api/H2SQLCondition.java"
exclude "stirling/software/SPDF/controller/api/UserController.java"
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
exclude "stirling/software/SPDF/model/api/Email.java"
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
exclude "stirling/software/SPDF/model/AttemptCounter.java"
exclude "stirling/software/SPDF/model/Authority.java"
exclude "stirling/software/SPDF/model/exception/BackupNotFoundException.java"
exclude "stirling/software/SPDF/model/exception/NoProviderFoundException.java"
exclude "stirling/software/SPDF/model/PersistentLogin.java"
exclude "stirling/software/SPDF/model/SessionEntity.java"
exclude "stirling/software/SPDF/model/User.java"
exclude "stirling/software/SPDF/repository/**"
}
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**'
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
}
@ -174,14 +81,15 @@ sourceSets {
test {
java {
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
exclude 'stirling/software/proprietary/security/**'
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
exclude "stirling/software/SPDF/config/security/**"
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java"
exclude "stirling/software/SPDF/controller/api/EmailControllerTest.java"
exclude "stirling/software/SPDF/repository/**"
}
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**'
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
}
}
@ -207,9 +115,10 @@ jpackage {
mainJar = "Stirling-PDF-${project.version}.jar"
appName = "Stirling PDF"
appVersion = project.version
// appVersion = "2005.45.1"
vendor = "Stirling PDF Inc"
appDescription = "Stirling PDF - Your Local PDF Editor"
icon = "stirling-pdf/src/main/resources/static/favicon.ico"
icon = "src/main/resources/static/favicon.ico"
verbose = true
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
@ -217,7 +126,6 @@ jpackage {
javaOptions = [
"-DBROWSER_OPEN=true",
"-DSTIRLING_PDF_DESKTOP_UI=true",
"-DDISABLE_ADDITIONAL_FEATURES=false",
"-Djava.awt.headless=false",
"-Dapple.awt.UIElement=true",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
@ -248,10 +156,10 @@ jpackage {
installDir = "C:/Program Files/Stirling-PDF"
}
// MacOS-specific configuration
// macOS-specific configuration
mac {
appVersion = getMacVersion(project.version.toString())
icon = "stirling-pdf/src/main/resources/static/favicon.icns"
icon = "src/main/resources/static/favicon.icns"
type = "dmg"
macPackageIdentifier = "Stirling PDF"
macPackageName = "Stirling PDF"
@ -273,7 +181,7 @@ jpackage {
// Linux-specific configuration
linux {
appVersion = project.version
icon = "stirling-pdf/src/main/resources/static/favicon.png"
icon = "src/main/resources/static/favicon.png"
type = "deb" // Can also use "rpm" for Red Hat-based systems
// Debian package configuration
@ -309,15 +217,10 @@ jpackage {
]*/
// Add copyright and license information
copyright = "Copyright © 2025 Stirling PDF Inc."
copyright = "Copyright © 2024 Stirling Software"
licenseFile = "LICENSE"
}
//tasks.wrapper {
// gradleVersion = "8.14"
// distributionType = Wrapper.DistributionType.ALL
//}
tasks.register('jpackageMacX64') {
group = 'distribution'
description = 'Packages app for MacOS x86_64'
@ -350,7 +253,7 @@ tasks.register('jpackageMacX64') {
'--main-class', 'org.springframework.boot.loader.launch.JarLauncher',
'--runtime-image', file(jrePath + "/zulu-17.jre/Contents/Home"),
'--dest', 'build/jpackage/x86_64',
'--icon', 'stirling-pdf/src/main/resources/static/favicon.icns',
'--icon', 'src/main/resources/static/favicon.icns',
'--app-version', getMacVersion(project.version.toString()),
'--mac-package-name', 'Stirling PDF (x86_64)',
'--mac-package-identifier', 'Stirling PDF (x86_64)',
@ -359,7 +262,6 @@ tasks.register('jpackageMacX64') {
// Java options
'--java-options', '-DBROWSER_OPEN=true',
'--java-options', '-DSTIRLING_PDF_DESKTOP_UI=true',
'--java-options', '-DDISABLE_ADDITIONAL_FEATURES=false',
'--java-options', '-Djava.awt.headless=false',
'--java-options', '-Dapple.awt.UIElement=true',
'--java-options', '--add-opens=java.base/java.lang=ALL-UNNAMED',
@ -388,6 +290,8 @@ tasks.register('jpackageMacX64') {
}
}
//jpackage.finalizedBy(jpackageMacX64)
tasks.register('downloadTempJre') {
group = 'distribution'
description = 'Downloads and extracts a temporary JRE'
@ -399,18 +303,18 @@ tasks.register('downloadTempJre') {
def jreArchive = new File(tmpDir, 'jre.tar.gz')
def jreDir = new File(tmpDir, 'jre')
println "Downloading JRE to $jreArchive"
println "🔽 Downloading JRE to $jreArchive..."
jreArchive.withOutputStream { out ->
new URI(jreUrl).toURL().withInputStream { from -> out << from }
}
println "Extracting JRE to $jreDir"
println "📦 Extracting JRE to $jreDir..."
jreDir.mkdirs()
providers.exec {
commandLine 'tar', '-xzf', jreArchive.absolutePath, '-C', jreDir.absolutePath, '--strip-components=1'
}.result.get()
println "JRE ready at: $jreDir"
println "JRE ready at: $jreDir"
ext.tempJrePath = jreDir.absolutePath
project.ext.tempJrePath = jreDir.absolutePath
} catch (Exception e) {
@ -436,7 +340,7 @@ tasks.register('cleanTempJre') {
}
launch4j {
icon = "${projectDir}/stirling-pdf/src/main/resources/static/favicon.ico"
icon = "${projectDir}/src/main/resources/static/favicon.ico"
outfile="Stirling-PDF.exe"
@ -447,7 +351,7 @@ launch4j {
}
jarTask = tasks.bootJar
errTitle="Encountered error, do you have Java 21?"
errTitle="Encountered error, Do you have Java 21?"
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
@ -470,12 +374,9 @@ launch4j {
spotless {
java {
target sourceSets.main.allJava
target project(':common').sourceSets.main.allJava
target project(':proprietary').sourceSets.main.allJava
target project(':stirling-pdf').sourceSets.main.allJava
target project.fileTree('src').include('**/*.java')
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")
toggleOffOn()
@ -490,12 +391,190 @@ sonar {
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
property "sonar.organization", "stirling-tools"
property "sonar.exclusions", "**/build-wrapper-dump.json, **/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
property "sonar.coverage.exclusions", "**/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
property "sonar.cpd.exclusions", "**/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
}
}
//gradleLint {
// rules=['unused-dependency']
// }
tasks.wrapper {
gradleVersion = "8.14"
distributionType = Wrapper.DistributionType.ALL
}
//tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation"
//}
configurations.all {
// Remove all commons-logging dependencies so that only spring-jcl is used
exclude group: 'commons-logging', module: 'commons-logging'
// Exclude Tomcat
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
//tmp for security bumps
implementation 'ch.qos.logback:logback-core:1.5.18'
implementation 'ch.qos.logback:logback-classic:1.5.18'
// Exclude vulnerable BouncyCastle version used in tableau
configurations.all {
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:132.3.1"
implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21"
}
//security updates
implementation "org.springframework:spring-webmvc:6.2.7"
implementation("io.github.pixee:java-security-toolkit:1.2.1")
// Exclude Tomcat and include Jetty
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
implementation 'com.posthog.java:posthog:1.2.0'
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-mail:$springBootVersion"
implementation "org.springframework.session:spring-session-core:3.4.3"
implementation "org.springframework:spring-jdbc:6.2.7"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232"
runtimeOnly "org.postgresql:postgresql:42.7.5"
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0'
}
implementation 'org.snakeyaml:snakeyaml-engine:2.9'
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
// Batik
implementation "org.apache.xmlgraphics:batik-all:1.19"
// TwelveMonkeys
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-bmp:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-hdr:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-icns:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-iff:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-jpeg:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-tiff:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
// Image metadata extractor
implementation "com.drewnoakes:metadata-extractor:2.19.0"
implementation "commons-io:commons-io:2.19.0"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8"
//general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation ("com.opencsv:opencsv:5.11")
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion")
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion")
// https://mvnrepository.com/artifact/technology.tabula/tabula
implementation ('technology.tabula:tabula:1.0.5') {
exclude group: "org.slf4j", module: "slf4j-simple"
exclude group: "org.bouncycastle", module: "bcprov-jdk15on"
exclude group: "com.google.code.gson", module: "gson"
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.15.0"
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0"
implementation "org.commonmark:commonmark-ext-gfm-tables:0.24.0"
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.6"
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
// Mockito (core)
testImplementation 'org.mockito:mockito-core:5.17.0'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
dependsOn "spotlessApply"
}
compileJava {
options.compilerArgs << "-parameters"
}
task writeVersion {
def propsFile = file("$projectDir/src/main/resources/version.properties")
def propsDir = propsFile.parentFile
doLast {
if (!propsDir.exists()) {
propsDir.mkdirs()
}
def props = new Properties()
props.setProperty("version", version)
props.store(propsFile.newWriter(), null)
}
}
processResources.dependsOn(writeVersion)
swaggerhubUpload {
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
api = "Stirling-PDF" // The name of your API on SwaggerHub
@ -506,49 +585,25 @@ swaggerhubUpload {
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
}
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
jar {
enabled = false
manifest {
attributes "Implementation-Title": "Stirling-PDF",
"Implementation-Version": project.version
}
}
tasks.named("test") {
useJUnitPlatform()
}
tasks.register('writeVersion') {
def propsFile = file("$projectDir/common/src/main/resources/version.properties")
def propsDir = propsFile.parentFile
doLast {
if (propsDir.exists()) {
if (propsFile.exists()) {
println "File exists: $propsFile"
} else {
println "$propsFile does not exist. Creating file."
propsFile.createNewFile()
}
} else {
println "Creating directory: $propsDir"
propsDir.mkdirs()
propsFile.createNewFile()
}
def props = new Properties()
props.setProperty("version", version)
props.store(propsFile.newWriter(), null)
}
}
processResources.dependsOn(writeVersion)
project(':stirling-pdf').tasks.bootJar.dependsOn(writeVersion)
tasks.register('printVersion') {
task printVersion {
doLast {
println project.version
}
}
tasks.register('printMacVersion') {
task printMacVersion {
doLast {
println getMacVersion(project.version.toString())
}
@ -557,22 +612,3 @@ tasks.register('printMacVersion') {
tasks.named('generateOpenApiDocs') {
doNotTrackState("Tracking state is not supported for this task")
}
tasks.named('bootRun') {
group = 'application'
description = 'Delegates to :stirling-pdf:bootRun'
dependsOn ':stirling-pdf:bootRun'
doFirst {
println "Delegating to :stirling-pdf:bootRun"
}
}
tasks.named('build') {
group = 'build'
description = 'Delegates to :stirling-pdf:bootJar'
dependsOn ':stirling-pdf:bootJar'
doFirst {
println "Delegating to :stirling-pdf:bootJar"
}
}

196
common/.gitignore vendored
View File

@ -1,196 +0,0 @@
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.exe
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
.classpath
.project
version.properties
#### Stirling-PDF Files ###
pipeline/watchedFolders/
pipeline/finishedFolders/
customFiles/
configs/
watchedFolders/
clientWebUI/
!cucumber/
!cucumber/exampleFiles/
!cucumber/exampleFiles/example_html.zip
exampleYmlFiles/stirling/
/testing/file_snapshots
SwaggerDoc.json
# Gradle
.gradle
.lock
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
*.db
/build
/common/build/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.pyo
# Virtual environments
.env*
.venv*
env*/
venv*/
ENV/
env.bak/
venv.bak/
# VS Code
/.vscode/**/*
!/.vscode/settings.json
!/.vscode/extensions.json
# IntelliJ IDEA
.idea/
*.iml
out/
# Ignore Mac DS_Store files
.DS_Store
**/.DS_Store
# cucumber
/cucumber/reports/**
# Certs and Security Files
*.p12
*.pk8
*.pem
*.crt
*.cer
*.cert
*.der
*.key
*.csr
*.kdbx
*.jks
*.asc
# SSH Keys
*.pub
*.priv
id_rsa
id_rsa.pub
id_ecdsa
id_ecdsa.pub
id_ed25519
id_ed25519.pub
.ssh/
*ssh
# cache
.cache
.ruff_cache
.mypy_cache
.pytest_cache
.ipynb_checkpoints
**/jcef-bundle/
# node_modules
node_modules/

View File

@ -1,20 +0,0 @@
// Configure bootRun to disable it or point to a main class
bootRun {
enabled = false
}
dependencies {
api 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-thymeleaf'
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
api 'com.fathzer:javaluator:3.0.6'
api 'com.posthog.java:posthog:1.2.0'
api 'org.apache.commons:commons-lang3:3.17.0'
api 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
api 'org.snakeyaml:snakeyaml-engine:2.9'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8"
api 'jakarta.mail:jakarta.mail-api:2.1.3'
}

View File

@ -1,39 +0,0 @@
package stirling.software.common.model.api.converters;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.common.model.api.PDFFile;
@Data
@EqualsAndHashCode(callSuper = true)
public class EmlToPdfRequest extends PDFFile {
// fileInput is inherited from PDFFile
@Schema(
description = "Include email attachments in the PDF output",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
example = "false")
private boolean includeAttachments = false;
@Schema(
description = "Maximum attachment size in MB to include (default 10MB, range: 1-100)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
example = "10",
minimum = "1",
maximum = "100")
private int maxAttachmentSizeMB = 10;
@Schema(
description = "Download HTML intermediate file instead of PDF",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
example = "false")
private boolean downloadHtml = false;
@Schema(
description = "Include CC and BCC recipients in header (if available)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
example = "true")
private boolean includeAllRecipients = true;
}

View File

@ -1,7 +0,0 @@
package stirling.software.common.model.exception;
public class UnsupportedClaimException extends RuntimeException {
public UnsupportedClaimException(String message) {
super(message);
}
}

View File

@ -1,14 +0,0 @@
package stirling.software.common.util;
import java.util.Collection;
public class ValidationUtil {
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

@ -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

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

View File

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

View File

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

View File

@ -14,11 +14,11 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "false"
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "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

View File

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

View File

@ -14,11 +14,11 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "false"
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite

View File

@ -14,10 +14,10 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Ultra-lite

View File

@ -14,11 +14,11 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
DOCKER_ENABLE_SECURITY: "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"
SYSTEM_DEFAULTLOCALE: en-US

View File

@ -14,11 +14,11 @@ services:
ports:
- 8080:8080
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "false"
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
PUID: 1002
PGID: 1002

196
proprietary/.gitignore vendored
View File

@ -1,196 +0,0 @@
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.exe
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
.classpath
.project
version.properties
#### Stirling-PDF Files ###
pipeline/watchedFolders/
pipeline/finishedFolders/
customFiles/
configs/
watchedFolders/
clientWebUI/
!cucumber/
!cucumber/exampleFiles/
!cucumber/exampleFiles/example_html.zip
exampleYmlFiles/stirling/
/testing/file_snapshots
SwaggerDoc.json
# Gradle
.gradle
.lock
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
*.db
/build
/proprietary/build/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.pyo
# Virtual environments
.env*
.venv*
env*/
venv*/
ENV/
env.bak/
venv.bak/
# VS Code
/.vscode/**/*
!/.vscode/settings.json
!/.vscode/extensions.json
# IntelliJ IDEA
.idea/
*.iml
out/
# Ignore Mac DS_Store files
.DS_Store
**/.DS_Store
# cucumber
/cucumber/reports/**
# Certs and Security Files
*.p12
*.pk8
*.pem
*.crt
*.cer
*.cert
*.der
*.key
*.csr
*.kdbx
*.jks
*.asc
# SSH Keys
*.pub
*.priv
id_rsa
id_rsa.pub
id_ecdsa
id_ecdsa.pub
id_ed25519
id_ed25519.pub
.ssh/
*ssh
# cache
.cache
.ruff_cache
.mypy_cache
.pytest_cache
.ipynb_checkpoints
**/jcef-bundle/
# node_modules
node_modules/

View File

@ -1,51 +0,0 @@
Stirling PDF User License
Copyright (c) 2025 Stirling PDF Inc.
License Scope & Usage Rights
Production use of the Stirling PDF Software is only permitted with a valid Stirling PDF User License.
For purposes of this license, “the Software” refers to the Stirling PDF application and any associated documentation files
provided by Stirling PDF Inc. You or your organization may not use the Software in production, at scale, or for business-critical
processes unless you have agreed to, and remain in compliance with, the Stirling PDF Subscription Terms of Service
(https://www.stirlingpdf.com/terms) or another valid agreement with Stirling PDF, and hold an active User License subscription
covering the appropriate number of licensed users.
Trial and Minimal Use
You may use the Software without a paid subscription for the sole purposes of internal trial, evaluation, or minimal use, provided that:
* Use is limited to the capabilities and restrictions defined by the Software itself;
* You do not copy, distribute, sublicense, reverse-engineer, or use the Software in client-facing or commercial contexts.
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,39 +0,0 @@
repositories {
maven { url = "https://build.shibboleth.net/maven/releases" }
}
bootRun {
enabled = false
}
dependencies {
implementation project(':common')
api 'org.springframework:spring-jdbc'
api 'org.springframework:spring-webmvc'
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'
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation 'org.bouncycastle:bcprov-jdk18on:1.80'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE'
api 'io.micrometer:micrometer-registry-prometheus'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database
runtimeOnly 'org.postgresql:postgresql:42.7.5'
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation 'com.coveo:saml-client:5.0.0'
}
tasks.register('prepareKotlinBuildScriptModel') {}

View File

@ -1,44 +0,0 @@
package stirling.software.proprietary.model;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.*;
import lombok.*;
import stirling.software.proprietary.security.model.User;
@Entity
@Table(name = "teams")
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
public class Team implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "team_id")
private Long id;
@Column(name = "name", unique = true, nullable = false)
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<User> users = new HashSet<>();
public void addUser(User user) {
users.add(user);
user.setTeam(this);
}
public void removeUser(User user) {
users.remove(user);
user.setTeam(null);
}
}

View File

@ -1,20 +0,0 @@
package stirling.software.proprietary.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class TeamWithUserCountDTO {
private Long id;
private String name;
private Long userCount;
// Constructor for JPQL projection
public TeamWithUserCountDTO(Long id, String name, Long userCount) {
this.id = id;
this.name = name;
this.userCount = userCount;
}
}

View File

@ -1,11 +0,0 @@
package stirling.software.proprietary.security.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to mark endpoints that require a Pro or higher license. */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PremiumEndpoint {}

View File

@ -1,30 +0,0 @@
package stirling.software.proprietary.security.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
@Aspect
@Component
public class PremiumEndpointAspect {
private final boolean runningProOrHigher;
public PremiumEndpointAspect(@Qualifier("runningProOrHigher") boolean runningProOrHigher) {
this.runningProOrHigher = runningProOrHigher;
}
@Around(
"@annotation(stirling.software.proprietary.security.config.PremiumEndpoint) || @within(stirling.software.proprietary.security.config.PremiumEndpoint)")
public Object checkPremiumAccess(ProceedingJoinPoint joinPoint) throws Throwable {
if (!runningProOrHigher) {
throw new ResponseStatusException(
HttpStatus.FORBIDDEN, "This endpoint requires a Pro or higher license");
}
return joinPoint.proceed();
}
}

View File

@ -1,127 +0,0 @@
package stirling.software.proprietary.security.controller.api;
import java.util.Optional;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.config.PremiumEndpoint;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.service.TeamService;
@Controller
@RequestMapping("/api/v1/team")
@Tag(name = "Team", description = "Team Management APIs")
@Slf4j
@RequiredArgsConstructor
@PremiumEndpoint
public class TeamController {
private final TeamRepository teamRepository;
private final UserRepository userRepository;
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/create")
public RedirectView createTeam(@RequestParam("name") String name) {
if (teamRepository.existsByNameIgnoreCase(name)) {
return new RedirectView("/adminSettings?messageType=teamExists");
}
Team team = new Team();
team.setName(name);
teamRepository.save(team);
return new RedirectView("/adminSettings?messageType=teamCreated");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/rename")
public RedirectView renameTeam(
@RequestParam("teamId") Long teamId, @RequestParam("newName") String newName) {
Optional<Team> existing = teamRepository.findById(teamId);
if (existing.isEmpty()) {
return new RedirectView("/adminSettings?messageType=teamNotFound");
}
if (teamRepository.existsByNameIgnoreCase(newName)) {
return new RedirectView("/adminSettings?messageType=teamNameExists");
}
Team team = existing.get();
// Prevent renaming the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible");
}
team.setName(newName);
teamRepository.save(team);
return new RedirectView("/adminSettings?messageType=teamRenamed");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/delete")
@Transactional
public RedirectView deleteTeam(@RequestParam("teamId") Long teamId) {
Optional<Team> teamOpt = teamRepository.findById(teamId);
if (teamOpt.isEmpty()) {
return new RedirectView("/adminSettings?messageType=teamNotFound");
}
Team team = teamOpt.get();
// Prevent deleting the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible");
}
long memberCount = userRepository.countByTeam(team);
if (memberCount > 0) {
return new RedirectView("/adminSettings?messageType=teamHasUsers");
}
teamRepository.delete(team);
return new RedirectView("/adminSettings?messageType=teamDeleted");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/addUser")
@Transactional
public RedirectView addUserToTeam(
@RequestParam("teamId") Long teamId,
@RequestParam("userId") Long userId) {
// Find the team
Team team = teamRepository.findById(teamId)
.orElseThrow(() -> new RuntimeException("Team not found"));
// Prevent adding users to the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/teams?error=internalTeamNotAccessible");
}
// Find the user
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
// Check if user is in the Internal team - prevent moving them
if (user.getTeam() != null && user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/teams/" + teamId + "?error=cannotMoveInternalUsers");
}
// Assign user to team
user.setTeam(team);
userRepository.save(user);
// Redirect back to team details page
return new RedirectView("/teams/" + teamId + "?messageType=userAdded");
}
}

View File

@ -1,105 +0,0 @@
package stirling.software.proprietary.security.controller.web;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.model.dto.TeamWithUserCountDTO;
import stirling.software.proprietary.security.database.repository.SessionRepository;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.service.TeamService;
@Controller
@RequestMapping("/teams")
@RequiredArgsConstructor
@Slf4j
public class TeamWebController {
private final TeamRepository teamRepository;
private final SessionRepository sessionRepository;
private final UserRepository userRepository;
@GetMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String listTeams(Model model) {
// Get teams with user counts using a DTO projection
List<TeamWithUserCountDTO> allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount();
// Filter out the Internal team
List<TeamWithUserCountDTO> teamsWithCounts = allTeamsWithCounts.stream()
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
.toList();
// Get the latest activity for each team
List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam();
// Convert the query results to a map for easy access in the view
Map<Long, Date> teamLastRequest = new HashMap<>();
for (Object[] result : teamActivities) {
Long teamId = (Long) result[0]; // teamId alias
Date lastActivity = (Date) result[1]; // lastActivity alias
teamLastRequest.put(teamId, lastActivity);
}
// Add data to the model
model.addAttribute("teamsWithCounts", teamsWithCounts);
model.addAttribute("teamLastRequest", teamLastRequest);
return "accounts/teams";
}
@GetMapping("/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String viewTeamDetails(@PathVariable("id") Long id, Model model) {
// Get the team
Team team = teamRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Team not found"));
// Prevent access to Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return "redirect:/teams?error=internalTeamNotAccessible";
}
// Get users for this team directly using the direct query
List<User> teamUsers = userRepository.findAllByTeamId(id);
// Get all users not in this team for the Add User to Team dropdown
// Exclude users that are in the Internal team
List<User> allUsers = userRepository.findAllWithTeam();
List<User> availableUsers = allUsers.stream()
.filter(user -> (user.getTeam() == null || !user.getTeam().getId().equals(id)) &&
(user.getTeam() == null || !user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)))
.toList();
// Get the latest session for each user in the team
List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id);
// Create a map of username to last request date
Map<String, Date> userLastRequest = new HashMap<>();
for (Object[] result : userSessions) {
String username = (String) result[0]; // username alias
Date lastRequest = (Date) result[1]; // lastRequest alias
userLastRequest.put(username, lastRequest);
}
model.addAttribute("team", team);
model.addAttribute("teamUsers", teamUsers);
model.addAttribute("availableUsers", availableUsers);
model.addAttribute("userLastRequest", userLastRequest);
return "accounts/team-details";
}
}

View File

@ -1,6 +0,0 @@
package stirling.software.proprietary.security.model;
public enum AuthenticationType {
WEB,
SSO
}

View File

@ -1,23 +0,0 @@
package stirling.software.proprietary.security.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.model.dto.TeamWithUserCountDTO;
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
Optional<Team> findByName(String name);
@Query("SELECT new stirling.software.proprietary.model.dto.TeamWithUserCountDTO(t.id, t.name, COUNT(u)) " +
"FROM Team t LEFT JOIN t.users u GROUP BY t.id, t.name")
List<TeamWithUserCountDTO> findAllTeamsWithUserCount();
boolean existsByNameIgnoreCase(String name);
}

View File

@ -1,40 +0,0 @@
package stirling.software.proprietary.security.service;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.repository.TeamRepository;
@Service
@RequiredArgsConstructor
public class TeamService {
private final TeamRepository teamRepository;
public static final String DEFAULT_TEAM_NAME = "Default";
public static final String INTERNAL_TEAM_NAME = "Internal";
public Team getOrCreateDefaultTeam() {
return teamRepository
.findByName(DEFAULT_TEAM_NAME)
.orElseGet(
() -> {
Team defaultTeam = new Team();
defaultTeam.setName(DEFAULT_TEAM_NAME);
return teamRepository.save(defaultTeam);
});
}
public Team getOrCreateInternalTeam() {
return teamRepository
.findByName(INTERNAL_TEAM_NAME)
.orElseGet(
() -> {
Team internalTeam = new Team();
internalTeam.setName(INTERNAL_TEAM_NAME);
return teamRepository.save(internalTeam);
});
}
}

View File

@ -1,387 +0,0 @@
/* modern-tables.css - Professional styling for data tables and related elements */
/* Main container - Reduced max-width from 1100px to 900px */
.data-container {
max-width: 900px;
margin: 2rem auto;
background-color: var(--md-sys-color-surface-container-lowest);
border-radius: 1rem;
padding: 0.5rem;
box-shadow: 0 2px 12px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.05);
}
/* Panel / Card */
.data-panel {
background-color: var(--md-sys-color-surface);
border-radius: 0.75rem;
box-shadow: 0 2px 8px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.08);
overflow: hidden;
}
/* Header */
.data-header {
display: flex;
align-items: center;
padding: 1.25rem 1.5rem;
background-color: var(--md-sys-color-surface-variant);
border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.data-title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
}
.data-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
border-radius: 0.5rem;
transition: all 0.2s ease;
}
/* Content area */
.data-body {
padding: 1.5rem;
background-color: var(--md-sys-color-surface-container-low);
border-radius: 0.5rem;
}
/* Action buttons container */
.data-actions {
display: flex;
justify-content: center;
margin: 1rem 0 1.5rem;
gap: 0.75rem;
}
/* Can add these classes for different alignments */
.data-actions-start {
justify-content: flex-start;
}
.data-actions-end {
justify-content: flex-end;
}
/* Button styling */
.data-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
text-decoration: none;
}
/* Fixed button colors - normal state has more contrast now */
.data-btn-primary {
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
.data-btn-primary:hover {
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-primary);
box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1);
}
.data-btn-secondary {
background-color: var(--md-sys-color-secondary);
color: var(--md-sys-color-on-secondary);
}
.data-btn-secondary:hover {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-secondary);
box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1);
}
.data-btn-danger {
background-color: var(--md-sys-color-error);
color: var(--md-sys-color-on-error);
}
.data-btn-danger:hover {
background-color: var(--md-sys-color-error-container);
color: var(--md-sys-color-error);
box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1);
}
.data-btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
/* Icon button */
.data-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
border: none;
cursor: pointer;
transition: all 0.2s ease;
background-color: transparent;
}
/* Fixed icon button colors */
.data-icon-btn-primary {
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
.data-icon-btn-primary:hover {
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-primary);
box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1);
}
.data-icon-btn-danger {
background-color: var(--md-sys-color-error);
color: var(--md-sys-color-on-error);
}
.data-icon-btn-danger:hover {
background-color: var(--md-sys-color-error-container);
color: var(--md-sys-color-error);
box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1);
}
/* Table styling */
.data-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
.data-table th {
text-align: left;
padding: 1rem;
background-color: var(--md-sys-color-surface-variant);
color: var(--md-sys-color-on-surface-variant);
font-weight: 600;
position: sticky;
top: 0;
}
.data-table th:first-child {
border-top-left-radius: 0.5rem;
}
.data-table th:last-child {
border-top-right-radius: 0.5rem;
}
.data-table td {
padding: 1rem;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.data-table tr:last-child td {
border-bottom: none;
}
.data-table tr:hover {
background-color: rgba(var(--md-sys-color-surface-variant-rgb), 0.5);
}
/* Table action cells */
.data-action-cell {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: flex-start;
}
.data-action-cell-center {
justify-content: center;
}
.data-action-cell-end {
justify-content: flex-end;
}
/* Status indicators */
.data-status {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border-radius: 1rem;
font-size: 0.875rem;
font-weight: 500;
}
.data-status-success {
background-color: var(--md-sys-color-tertiary-container);
color: var(--md-sys-color-tertiary);
}
.data-status-danger {
background-color: var(--md-sys-color-error-container);
color: var(--md-sys-color-error);
}
.data-status-warning {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-secondary);
}
.data-status-info {
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-primary);
}
/* Stats/Info container */
.data-stats {
display: flex;
gap: 1.5rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.data-stat-card {
background-color: var(--md-sys-color-surface-variant);
border-radius: 0.5rem;
padding: 1.25rem;
flex: 1;
min-width: 180px;
box-shadow: 0 2px 8px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.05);
}
.data-stat-label {
font-size: 0.875rem;
color: var(--md-sys-color-on-surface-variant);
margin-bottom: 0.5rem;
}
.data-stat-value {
font-size: 1.75rem;
font-weight: 700;
color: var(--md-sys-color-on-surface);
}
/* Section title */
.data-section-title {
font-size: 1.25rem;
font-weight: 600;
margin: 1.5rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
/* Empty state styling */
.data-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--md-sys-color-on-surface-variant);
}
.data-empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.7;
}
.data-empty-text {
font-size: 1.125rem;
margin-bottom: 1.5rem;
}
/* Modal styling */
.data-modal {
border-radius: 0.75rem;
overflow: hidden;
}
.data-modal-header {
background-color: var(--md-sys-color-surface-variant);
padding: 1.25rem;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
display: flex;
align-items: center;
justify-content: space-between;
}
.data-modal-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
}
/* Modal close button styling */
.data-btn-close {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: var(--md-sys-color-surface-variant);
color: var(--md-sys-color-on-surface-variant);
border: none;
cursor: pointer;
transition: all 0.2s ease;
padding: 0;
margin: 0;
}
.data-btn-close:hover {
background-color: var(--md-sys-color-surface-container-high);
color: var(--md-sys-color-on-surface);
}
.data-btn-close .material-symbols-rounded {
font-size: 1.25rem;
}
.data-modal-body {
padding: 1.5rem;
}
.data-modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--md-sys-color-outline-variant);
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
/* Form elements */
.data-form-group {
margin-bottom: 1.25rem;
}
.data-form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.data-form-control {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border: 1px solid
}

View File

@ -1,196 +0,0 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{team.details.title}, header=#{team.details.header})}"></th:block>
<link rel="stylesheet" th:href="@{/css/modern-tables.css}">
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<div class="data-container">
<div class="data-panel">
<div class="data-header">
<h1 class="data-title">
<span class="data-icon">
<span class="material-symbols-rounded">group</span>
</span>
<span th:text="'Team: ' + ${team.name}">Team Name</span>
</h1>
</div>
<div class="data-body">
<div class="data-stats">
<div class="data-stat-card">
<div class="data-stat-label">Total Members:</div>
<div class="data-stat-value" th:text="${teamUsers.size()}">1</div>
</div>
</div>
<div class="data-actions data-actions-start">
<a th:href="@{'/teams'}" class="data-btn data-btn-secondary">
<span class="material-symbols-rounded">arrow_back</span>
<span th:text="#{team.back}">Back to Teams</span>
</a>
</div>
<div class="data-section-title">Members</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Role</th>
<th scope="col" th:title="${@runningProOrHigher} ? #{adminUserSettings.lastRequest} : 'Pro feature'" class="text-overflow" th:text="#{adminUserSettings.lastRequest}">Last Request</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${teamUsers}">
<td th:text="${user.id}">1</td>
<td th:text="${user.username}">username</td>
<td th:text="#{${user.roleName}}">Role</td>
<td th:text="${@runningProOrHigher} ? (${userLastRequest[user.username] != null ? #dates.format(userLastRequest[user.username], 'yyyy-MM-dd HH:mm:ss') : 'N/A'}) : 'hidden'">2023-01-01 12:00:00</td>
<td>
<span th:if="${user.enabled}" class="data-status data-status-success">
<span class="material-symbols-rounded">person</span>
Enabled
</span>
<span th:unless="${user.enabled}" class="data-status data-status-danger">
<span class="material-symbols-rounded">person_off</span>
Disabled
</span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Empty state for when there are no team members -->
<div th:if="${teamUsers.empty}" class="data-empty">
<span class="material-symbols-rounded data-empty-icon">person_off</span>
<p class="data-empty-text">This team has no members yet.</p>
<button data-bs-toggle="modal" data-bs-target="#addUserToTeamModal" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">person_add</span>
<span th:text="#{team.addUser}">Add User to Team</span>
</button>
</div>
<!-- Add button for non-empty teams too -->
<div th:if="${!teamUsers.empty}" class="data-actions data-mt-3">
<button data-bs-toggle="modal" data-bs-target="#addUserToTeamModal" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">person_add</span>
<span th:text="#{team.addUser}">Add User to Team</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript for team warning -->
<script th:inline="javascript">
function checkUserTeam(userId) {
// Clear any existing warning
const warningDiv = document.getElementById('teamChangeWarning');
const warningMessage = document.getElementById('warningMessage');
const submitButton = document.getElementById('addUserSubmitBtn');
// Reset
warningDiv.style.display = 'none';
submitButton.onclick = null;
// Get the selected option
const selectedOption = document.querySelector('#userId option[value="' + userId + '"]');
if (!selectedOption) return;
// Get team data
const currentTeam = selectedOption.getAttribute('data-team');
const currentTeamId = selectedOption.getAttribute('data-team-id');
const newTeamName = /*[[${team.name}]]*/ 'Current Team';
// If user is already in a team, show warning
if (currentTeam && currentTeam.length > 0) {
// Use internationalized message
const warningTemplate = /*[[#{team.warning.moveUser}]]*/ 'Warning: This will move the user from "{0}" team to "{1}" team. Are you sure?';
const formattedWarning = warningTemplate.replace('{0}', currentTeam).replace('{1}', newTeamName);
warningMessage.textContent = formattedWarning;
warningDiv.style.display = 'block';
// Add confirmation to submit button
submitButton.onclick = function(e) {
// Use internationalized message
const confirmTemplate = /*[[#{team.confirm.moveUser}]]*/ 'Are you sure you want to move this user from "{0}" team to "{1}" team?';
const formattedConfirm = confirmTemplate.replace('{0}', currentTeam).replace('{1}', newTeamName);
if (!confirm(formattedConfirm)) {
e.preventDefault();
return false;
}
return true;
};
}
}
</script>
<!-- Add User to Team Modal -->
<div class="modal fade" id="addUserToTeamModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form th:action="@{'/api/v1/team/addUser'}" method="post" class="modal-content data-modal">
<div class="data-modal-header">
<h5 class="data-modal-title">
<span class="data-icon">
<span class="material-symbols-rounded">person_add</span>
</span>
<span th:text="#{team.addUser}">Add User to Team</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="data-modal-body">
<input type="hidden" name="teamId" th:value="${team.id}" />
<div class="data-form-group">
<label for="userId" class="data-form-label" th:text="#{team.selectUser}">Select User</label>
<select name="userId" id="userId" class="data-form-control" required onchange="checkUserTeam(this.value)">
<option value="" disabled selected th:text="#{selectFillter}">-- Select User --</option>
<option th:each="user : ${availableUsers}"
th:value="${user.id}"
th:text="${user.username}"
th:data-team="${user.team != null ? user.team.name : ''}"
th:data-team-id="${user.team != null ? user.team.id : ''}">
Username
</option>
</select>
</div>
<!-- Warning message for users being moved between teams -->
<div id="teamChangeWarning" class="alert alert-warning mt-3" style="display: none;">
<span class="material-symbols-rounded">warning</span>
<span id="warningMessage">Warning: This will move the user from their current team to this team.</span>
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" id="addUserSubmitBtn" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{team.addUser}">Add User</span>
</button>
</div>
</div>
</form>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>

View File

@ -1,133 +0,0 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.manageTeams}, header=#{adminUserSettings.manageTeams})}"></th:block>
<link rel="stylesheet" th:href="@{/css/modern-tables.css}">
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<div class="data-container">
<div class="data-panel">
<div class="data-header">
<h1 class="data-title">
<span class="data-icon">
<span class="material-symbols-rounded">groups</span>
</span>
<span th:text="#{adminUserSettings.manageTeams}">Team Management</span>
</h1>
</div>
<div class="data-body">
<!-- Back Button -->
<div class="data-actions data-actions-start">
<a href="/adminSettings" class="data-btn data-btn-secondary">
<span class="material-symbols-rounded">arrow_back</span>
<span th:text="#{back.toSettings}">Back to Settings</span>
</a>
</div>
<!-- Create New Team Button -->
<div class="data-actions">
<a href="#"
th:data-bs-toggle="${@runningProOrHigher} ? 'modal' : null"
th:data-bs-target="${@runningProOrHigher} ? '#addTeamModal' : null"
th:class="${@runningProOrHigher} ? 'data-btn data-btn-primary' : 'data-btn data-btn-danger'"
th:title="${@runningProOrHigher} ? #{adminUserSettings.createTeam} : #{enterpriseEdition.proTeamFeatureDisabled}">
<span class="material-symbols-rounded">group_add</span>
<span th:text="#{adminUserSettings.createTeam}">Create New Team</span>
</a>
</div>
<!-- Team Table -->
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th scope="col" th:text="#{adminUserSettings.teamName}">Team Name</th>
<th scope="col" th:text="#{adminUserSettings.totalMembers}">Total Members</th>
<th scope="col" th:title="${@runningProOrHigher} ? #{adminUserSettings.lastRequest} : 'Pro feature'" class="text-overflow" th:text="#{adminUserSettings.lastRequest}">Last Request</th>
<th scope="col" th:text="#{adminUserSettings.actions}">Actions</th>
</tr>
</thead>
<tbody>
<!-- Try approach 1 - DTO projection -->
<tr th:each="teamDto : ${teamsWithCounts}">
<td th:text="${teamDto.name}"></td>
<td th:text="${teamDto.userCount}"></td>
<td th:text="${@runningProOrHigher} ? (${teamLastRequest[teamDto.id] != null ? #dates.format(teamLastRequest[teamDto.id], 'yyyy-MM-dd HH:mm:ss') : 'N/A'}) : 'hidden'"></td>
<td>
<div class="data-action-cell">
<a th:href="@{'/teams/' + ${teamDto.id}}" class="data-btn data-btn-secondary data-btn-sm" th:title="#{adminUserSettings.viewTeam}">
<span class="material-symbols-rounded">search</span> <span th:text="#{view}">View</span>
</a>
<form th:action="@{'/api/v1/team/delete'}" method="post" style="display:inline-block"
onsubmit="return confirmDeleteTeam()">
<input type="hidden" name="teamId" th:value="${teamDto.id}" />
<button type="submit" class="data-btn data-btn-danger data-btn-sm"
th:disabled="${!@runningProOrHigher}"
th:classappend="${!@runningProOrHigher} ? 'disabled' : ''"
th:title="${@runningProOrHigher} ? #{adminUserSettings.deleteTeam} : #{enterpriseEdition.proTeamFeatureDisabled}">
<span class="material-symbols-rounded">delete</span> <span th:text="#{delete}">Delete</span>
</button>
</form>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Delete Confirmation Script -->
<script th:inline="javascript">
const confirmDeleteText = /*[[#{adminUserSettings.confirmDeleteTeam}]]*/ 'Are you sure you want to delete this team?';
function confirmDeleteTeam() {
return confirm(confirmDeleteText);
}
</script>
</div>
</div>
</div>
<!-- Add Team Modal -->
<div class="modal fade" id="addTeamModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form th:action="@{'/api/v1/team/create'}" method="post" class="modal-content data-modal">
<div class="data-modal-header">
<h5 class="data-modal-title">
<span class="data-icon">
<span class="material-symbols-rounded">group_add</span>
</span>
<span th:text="#{adminUserSettings.createTeam}">Create Team</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="data-modal-body">
<div class="data-form-group">
<label for="teamName" class="data-form-label" th:text="#{adminUserSettings.teamName}">Team Name</label>
<input type="text" name="name" id="teamName" class="data-form-control" required />
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{adminUserSettings.createTeam}">Create</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>

View File

@ -1,83 +0,0 @@
package stirling.software.proprietary.security.service;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.repository.TeamRepository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TeamServiceTest {
@Mock
private TeamRepository teamRepository;
@InjectMocks
private TeamService teamService;
@Test
void getDefaultTeam() {
var team = new Team();
team.setName("Marleyans");
when(teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME))
.thenReturn(Optional.of(team));
Team result = teamService.getOrCreateDefaultTeam();
assertEquals(team, result);
}
@Test
void createDefaultTeam_whenRepositoryIsEmpty() {
String teamName = "Default";
var defaultTeam = new Team();
defaultTeam.setId(1L);
defaultTeam.setName(teamName);
when(teamRepository.findByName(teamName))
.thenReturn(Optional.empty());
when(teamRepository.save(any(Team.class))).thenReturn(defaultTeam);
Team result = teamService.getOrCreateDefaultTeam();
assertEquals(TeamService.DEFAULT_TEAM_NAME, result.getName());
}
@Test
void getInternalTeam() {
var team = new Team();
team.setName("Eldians");
when(teamRepository.findByName(TeamService.INTERNAL_TEAM_NAME))
.thenReturn(Optional.of(team));
Team result = teamService.getOrCreateInternalTeam();
assertEquals(team, result);
}
@Test
void createInternalTeam_whenRepositoryIsEmpty() {
String teamName = "Internal";
Team internalTeam = new Team();
internalTeam.setId(2L);
internalTeam.setName(teamName);
when(teamRepository.findByName(teamName))
.thenReturn(Optional.empty());
when(teamRepository.save(any(Team.class))).thenReturn(internalTeam);
when(teamRepository.findByName(TeamService.INTERNAL_TEAM_NAME))
.thenReturn(Optional.empty());
Team result = teamService.getOrCreateInternalTeam();
assertEquals(internalTeam, result);
}
}

View File

@ -1,317 +0,0 @@
package stirling.software.proprietary.security.service;
import java.sql.SQLException;
import java.util.Locale;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.MessageSource;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.database.repository.AuthorityRepository;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private TeamRepository teamRepository;
@Mock
private AuthorityRepository authorityRepository;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
private MessageSource messageSource;
@Mock
private SessionPersistentRegistry sessionPersistentRegistry;
@Mock
private DatabaseServiceInterface databaseService;
@Mock
private ApplicationProperties.Security.OAUTH2 oauth2Properties;
@InjectMocks
private UserService userService;
private Team mockTeam;
private User mockUser;
@BeforeEach
void setUp() {
mockTeam = new Team();
mockTeam.setId(1L);
mockTeam.setName("Test Team");
mockUser = new User();
mockUser.setId(1L);
mockUser.setUsername("testuser");
mockUser.setEnabled(true);
}
@Test
void testSaveUser_WithUsernameAndAuthenticationType_Success() throws Exception {
// Given
String username = "testuser";
AuthenticationType authType = AuthenticationType.WEB;
when(teamRepository.findByName("Default")).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
userService.saveUser(username, authType);
// Then
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithUsernamePasswordAndTeamId_Success() throws Exception {
// Given
String username = "testuser";
String password = "password123";
Long teamId = 1L;
String encodedPassword = "encodedPassword123";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
User result = userService.saveUser(username, password, teamId);
// Then
assertNotNull(result);
verify(passwordEncoder).encode(password);
verify(teamRepository).findById(teamId);
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithTeamAndRole_Success() throws Exception {
// Given
String username = "testuser";
String password = "password123";
String role = Role.ADMIN.getRoleId();
boolean firstLogin = true;
String encodedPassword = "encodedPassword123";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
User result = userService.saveUser(username, password, mockTeam, role, firstLogin);
// Then
assertNotNull(result);
verify(passwordEncoder).encode(password);
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithInvalidUsername_ThrowsException() throws Exception {
// Given
String invalidUsername = "ab"; // Too short (less than 3 characters)
AuthenticationType authType = AuthenticationType.WEB;
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.saveUser(invalidUsername, authType)
);
verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase();
}
@Test
void testSaveUser_WithNullPassword_Success() throws Exception {
// Given
String username = "testuser";
Long teamId = 1L;
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
User result = userService.saveUser(username, null, teamId);
// Then
assertNotNull(result);
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithEmptyPassword_Success() throws Exception {
// Given
String username = "testuser";
String emptyPassword = "";
Long teamId = 1L;
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
User result = userService.saveUser(username, emptyPassword, teamId);
// Then
assertNotNull(result);
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithValidEmail_Success() throws Exception {
// Given
String emailUsername = "test@example.com";
AuthenticationType authType = AuthenticationType.SSO;
when(teamRepository.findByName("Default")).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
userService.saveUser(emailUsername, authType);
// Then
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithReservedUsername_ThrowsException() throws Exception {
// Given
String reservedUsername = "all_users";
AuthenticationType authType = AuthenticationType.WEB;
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.saveUser(reservedUsername, authType)
);
verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase();
}
@Test
void testSaveUser_WithAnonymousUser_ThrowsException() throws Exception {
// Given
String anonymousUsername = "anonymoususer";
AuthenticationType authType = AuthenticationType.WEB;
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.saveUser(anonymousUsername, authType)
);
verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase();
}
@Test
void testSaveUser_DatabaseExportThrowsException_StillSavesUser() throws Exception {
// Given
String username = "testuser";
String password = "password123";
Long teamId = 1L;
String encodedPassword = "encodedPassword123";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doThrow(new SQLException("Database export failed")).when(databaseService).exportDatabase();
// When & Then
assertThrows(SQLException.class, () -> userService.saveUser(username, password, teamId));
// Verify user was still saved before the exception
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithFirstLoginFlag_Success() throws Exception {
// Given
String username = "testuser";
String password = "password123";
Long teamId = 1L;
boolean firstLogin = true;
boolean enabled = false;
String encodedPassword = "encodedPassword123";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
userService.saveUser(username, password, teamId, firstLogin, enabled);
// Then
verify(passwordEncoder).encode(password);
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
@Test
void testSaveUser_WithCustomRole_Success() throws Exception {
// Given
String username = "testuser";
String password = "password123";
Long teamId = 1L;
String customRole = Role.LIMITED_API_USER.getRoleId();
String encodedPassword = "encodedPassword123";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(databaseService).exportDatabase();
// When
userService.saveUser(username, password, teamId, customRole);
// Then
verify(passwordEncoder).encode(password);
verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase();
}
}

View File

@ -182,8 +182,7 @@ def compare_files(
sort_ignore_translation[language]["ignore"].remove(
default_key.strip()
)
except ValueError as e:
print(f"Error processing line {line_num} in {file_path}: {e}")
except ValueError:
print(f"{line_default}|{line_file}")
exit(1)
except IndexError:
@ -207,7 +206,7 @@ def compare_files(
if __name__ == "__main__":
directory = os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")
directory = os.path.join(os.getcwd(), "src", "main", "resources")
messages_file_paths = glob.glob(os.path.join(directory, "messages_*.properties"))
reference_file = os.path.join(directory, "messages_en_GB.properties")

View File

@ -1,6 +1,6 @@
echo "Running Stirling PDF with DISABLE_ADDITIONAL_FEATURES=${DISABLE_ADDITIONAL_FEATURES} and VERSION_TAG=${VERSION_TAG}"
# Check for DISABLE_ADDITIONAL_FEATURES and download the appropriate JAR if required
if [ "$DISABLE_ADDITIONAL_FEATURES" = "false" ] && [ "$VERSION_TAG" != "alpha" ]; then
echo "Running Stirling PDF with DOCKER_ENABLE_SECURITY=${DOCKER_ENABLE_SECURITY} and VERSION_TAG=${VERSION_TAG}"
# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required
if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
if [ ! -f app-security.jar ]; then
echo "Trying to download from: https://files.stirlingpdf.com/v$VERSION_TAG/Stirling-PDF-with-login.jar"
curl -L -o app-security.jar https://files.stirlingpdf.com/v$VERSION_TAG/Stirling-PDF-with-login.jar

View File

@ -1,190 +1,34 @@
[ar_AR]
ignore = [
'lang.div',
'lang.dzo',
'lang.que',
'language.direction',
]
[az_AZ]
ignore = [
'lang.afr',
'lang.bre',
'lang.div',
'lang.epo',
'lang.guj',
'lang.hin',
'lang.kan',
'lang.mal',
'lang.mar',
'lang.mlt',
'lang.mri',
'lang.msa',
'lang.nep',
'lang.ori',
'lang.pan',
'lang.san',
'lang.sin',
'lang.slk',
'lang.snd',
'lang.sun',
'lang.tam',
'lang.tat',
'lang.urd',
'lang.yor',
'language.direction',
]
[bg_BG]
ignore = [
'lang.div',
'lang.dzo',
'lang.iku',
'lang.que',
'language.direction',
]
[bo_CN]
ignore = [
'language.direction',
]
[ca_CA]
ignore = [
'PDFToText.tags',
'adminUserSettings.admin',
'lang.amh',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.fao',
'lang.fry',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.kan',
'lang.kaz',
'lang.lao',
'lang.mar',
'lang.mri',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.snd',
'lang.swa',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tir',
'lang.uzb',
'lang.uzb_cyrl',
'language.direction',
'watermark.type.1',
]
[cs_CZ]
ignore = [
'lang.amh',
'lang.asm',
'lang.bod',
'lang.bos',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.frk',
'lang.gla',
'lang.guj',
'lang.iku',
'lang.jav',
'lang.kan',
'lang.kat',
'lang.khm',
'lang.kir',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.msa',
'lang.nor',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.san',
'lang.sin',
'lang.snd',
'lang.sun',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgl',
'lang.tha',
'lang.tir',
'lang.uig',
'lang.urd',
'lang.uzb',
'lang.uzb_cyrl',
'lang.yor',
'language.direction',
'text',
]
[da_DK]
ignore = [
'lang.afr',
'lang.amh',
'lang.ben',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.frk',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.jav',
'lang.kan',
'lang.khm',
'lang.lao',
'lang.lat',
'lang.ltz',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.msa',
'lang.nep',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.san',
'lang.sin',
'lang.slk_frak',
'lang.snd',
'lang.sun',
'lang.swa',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tha',
'lang.tir',
'lang.ton',
'lang.uig',
'lang.urd',
'lang.uzb',
'lang.yor',
'language.direction',
]
@ -197,36 +41,8 @@ ignore = [
'addPageNumbers.selectText.3',
'alphabet',
'certSign.name',
'cookieBanner.popUp.acceptAllBtn',
'endpointStatistics.top10',
'endpointStatistics.top20',
'fileChooser.dragAndDrop',
'home.pipeline.title',
'lang.afr',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.kan',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.nep',
'lang.ori',
'lang.pan',
'lang.que',
'lang.san',
'lang.snd',
'lang.tam',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.urd',
'lang.yor',
'language.direction',
'legal.impressum',
'licenses.version',
@ -240,19 +56,13 @@ ignore = [
'validateSignature.cert.version',
'validateSignature.status',
'watermark.type.1',
'endpointStatistics.top10',
'endpointStatistics.top20',
'cookieBanner.popUp.acceptAllBtn',
]
[el_GR]
ignore = [
'lang.ceb',
'lang.dzo',
'lang.iku',
'lang.ori',
'lang.pan',
'lang.que',
'lang.sin',
'lang.uig',
'lang.uzb_cyrl',
'language.direction',
]
@ -260,31 +70,6 @@ ignore = [
ignore = [
'adminUserSettings.roles',
'error',
'lang.asm',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.fil',
'lang.frm',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.san',
'lang.snd',
'lang.sun',
'lang.tam',
'lang.tel',
'lang.tir',
'lang.urd',
'lang.uzb',
'lang.yor',
'language.direction',
'no',
'showJS.tags',
@ -292,23 +77,6 @@ ignore = [
[eu_ES]
ignore = [
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.mal',
'lang.pan',
'lang.que',
'lang.san',
'lang.slv',
'lang.snd',
'lang.sqi',
'lang.tat',
'lang.tir',
'lang.yor',
'language.direction',
]
@ -328,31 +96,6 @@ ignore = [
'alphabet',
'compare.document.1',
'compare.document.2',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.eus',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.kaz',
'lang.khm',
'lang.lao',
'lang.ltz',
'lang.mal',
'lang.mar',
'lang.oci',
'lang.ori',
'lang.que',
'lang.san',
'lang.snd',
'lang.swa',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.yor',
'language.direction',
'licenses.license',
'licenses.module',
@ -365,24 +108,6 @@ ignore = [
[ga_IE]
ignore = [
'lang.ceb',
'lang.cos',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.guj',
'lang.hat',
'lang.iku',
'lang.lao',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.sin',
'lang.snd',
'lang.sun',
'lang.tgk',
'lang.tir',
'lang.uig',
'language.direction',
]
@ -395,126 +120,22 @@ ignore = [
ignore = [
'PDFToBook.selectText.1',
'home.pipeline.title',
'lang.bod',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.dzo',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mri',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.san',
'lang.snd',
'lang.tam',
'lang.tel',
'lang.tgl',
'lang.tir',
'language.direction',
'showJS.tags',
]
[hu_HU]
ignore = [
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.fao',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mar',
'lang.mri',
'lang.ori',
'lang.que',
'lang.tel',
'lang.tgl',
'language.direction',
]
[id_ID]
ignore = [
'lang.aze',
'lang.aze_cyrl',
'lang.bre',
'lang.cat',
'lang.ceb',
'lang.chr',
'lang.cym',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.eus',
'lang.fao',
'lang.frk',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.kan',
'lang.kaz',
'lang.kir',
'lang.lao',
'lang.lat',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.slk_frak',
'lang.snd',
'lang.sun',
'lang.swa',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tha',
'lang.tir',
'lang.uig',
'lang.urd',
'lang.uzb',
'lang.uzb_cyrl',
'lang.yor',
'language.direction',
]
[it_IT]
ignore = [
'lang.asm',
'lang.aze',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.fao',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.snd',
'lang.swa',
'lang.tam',
'lang.tel',
'lang.tgl',
'lang.urd',
'lang.yor',
'language.direction',
'no',
'password',
@ -527,21 +148,11 @@ ignore = [
[ja_JP]
ignore = [
'lang.jav',
'language.direction',
]
[ko_KR]
ignore = [
'lang.fao',
'lang.pus',
'lang.sun',
'language.direction',
]
[ml_IN]
ignore = [
'lang.iku',
'language.direction',
]
@ -549,37 +160,6 @@ ignore = [
ignore = [
'compare.document.1',
'compare.document.2',
'lang.afr',
'lang.asm',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.dzo',
'lang.epo',
'lang.fao',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.sin',
'lang.snd',
'lang.sun',
'lang.swa',
'lang.tam',
'lang.tel',
'lang.tgl',
'lang.ton',
'lang.urd',
'lang.yor',
'language.direction',
'navbar.allTools',
'sponsor',
@ -590,49 +170,6 @@ ignore = [
'PDFToBook.selectText.1',
'adminUserSettings.admin',
'info',
'lang.afr',
'lang.amh',
'lang.ben',
'lang.bos',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.dan_frak',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.guj',
'lang.hin',
'lang.iku',
'lang.kan',
'lang.khm',
'lang.lao',
'lang.lat',
'lang.ltz',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.msa',
'lang.nep',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.que',
'lang.san',
'lang.slk_frak',
'lang.snd',
'lang.swa',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tha',
'lang.tir',
'lang.ton',
'lang.uig',
'lang.urd',
'lang.yor',
'language.direction',
'oops',
'sponsor',
@ -641,148 +178,27 @@ ignore = [
[pl_PL]
ignore = [
'PDFToBook.selectText.1',
'lang.afr',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.cos',
'lang.div',
'lang.dzo',
'lang.fao',
'lang.frk',
'lang.guj',
'lang.hat',
'lang.iku',
'lang.kan',
'lang.khm',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.nep',
'lang.oci',
'lang.ori',
'lang.pus',
'lang.que',
'lang.snd',
'lang.sun',
'lang.swa',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.uig',
'lang.urd',
'language.direction',
]
[pt_BR]
ignore = [
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.fao',
'lang.fil',
'lang.frk',
'lang.fry',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.kir',
'lang.mar',
'lang.ori',
'lang.pan',
'lang.que',
'lang.snd',
'lang.tat',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.uig',
'lang.uzb',
'lang.yid',
'language.direction',
'pipelineOptions.pipelineHeader',
]
[pt_PT]
ignore = [
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.fao',
'lang.fil',
'lang.frk',
'lang.fry',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.kir',
'lang.mar',
'lang.ori',
'lang.pan',
'lang.que',
'lang.snd',
'lang.tat',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.uig',
'lang.uzb',
'lang.yid',
'language.direction',
]
[ro_RO]
ignore = [
'lang.amh',
'lang.asm',
'lang.bod',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.cos',
'lang.deu_frak',
'lang.div',
'lang.dzo',
'lang.est',
'lang.fao',
'lang.glg',
'lang.guj',
'lang.iku',
'lang.jav',
'lang.kan',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.nep',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.pus',
'lang.slk_frak',
'lang.snd',
'lang.sun',
'lang.swa',
'lang.tam',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.urd',
'lang.yor',
'language.direction',
]
[ru_RU]
ignore = [
'lang.iku',
'lang.pus',
'language.direction',
]
@ -791,25 +207,6 @@ ignore = [
'adminUserSettings.admin',
'home.multiTool.title',
'info',
'lang.ceb',
'lang.chr',
'lang.dzo',
'lang.epo',
'lang.iku',
'lang.kaz',
'lang.mar',
'lang.ori',
'lang.pan',
'lang.que',
'lang.san',
'lang.sin',
'lang.snd',
'lang.tat',
'lang.tel',
'lang.tgl',
'lang.tir',
'lang.urd',
'lang.uzb',
'language.direction',
'navbar.sections.security',
'text',
@ -818,37 +215,6 @@ ignore = [
[sl_SI]
ignore = [
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.eus',
'lang.fao',
'lang.frk',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.lao',
'lang.mar',
'lang.mri',
'lang.oci',
'lang.ori',
'lang.pan',
'lang.que',
'lang.slk',
'lang.snd',
'lang.sun',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tir',
'lang.urd',
'lang.uzb',
'lang.yor',
'language.direction',
]
@ -861,43 +227,11 @@ ignore = [
[sv_SE]
ignore = [
'lang.ben',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.dzo',
'lang.epo',
'lang.guj',
'lang.hin',
'lang.kan',
'lang.lao',
'lang.lat',
'lang.mal',
'lang.mri',
'lang.ori',
'lang.pan',
'lang.que',
'lang.san',
'lang.slk_frak',
'lang.snd',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tir',
'lang.urd',
'lang.yor',
'language.direction',
]
[th_TH]
ignore = [
'lang.dzo',
'lang.kir',
'lang.pan',
'lang.sin',
'lang.slk_frak',
'lang.tir',
'lang.uzb_cyrl',
'language.direction',
'pipelineOptions.pipelineHeader',
'showJS.tags',
@ -905,111 +239,33 @@ ignore = [
[tr_TR]
ignore = [
'lang.afr',
'lang.bre',
'lang.ceb',
'lang.chr',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.fao',
'lang.guj',
'lang.kan',
'lang.lao',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.msa',
'lang.ori',
'lang.pus',
'lang.que',
'lang.sin',
'lang.slk',
'lang.slk_frak',
'lang.snd',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tgl',
'lang.tir',
'lang.urd',
'lang.yor',
'language.direction',
]
[uk_UA]
ignore = [
'lang.iku',
'language.direction',
]
[vi_VN]
ignore = [
'lang.amh',
'lang.asm',
'lang.aze',
'lang.aze_cyrl',
'lang.bos',
'lang.bre',
'lang.cat',
'lang.ceb',
'lang.chr',
'lang.cos',
'lang.div',
'lang.dzo',
'lang.epo',
'lang.eus',
'lang.fao',
'lang.glg',
'lang.guj',
'lang.iku',
'lang.kan',
'lang.kaz',
'lang.kir',
'lang.lat',
'lang.ltz',
'lang.mal',
'lang.mar',
'lang.mri',
'lang.msa',
'lang.ori',
'lang.pus',
'lang.que',
'lang.sin',
'lang.slk',
'lang.slk_frak',
'lang.snd',
'lang.swa',
'lang.syr',
'lang.tam',
'lang.tat',
'lang.tel',
'lang.tgk',
'lang.tir',
'lang.uig',
'lang.uzb',
'lang.uzb_cyrl',
'lang.yid',
'lang.yor',
'language.direction',
'pipeline.title',
'pipelineOptions.pipelineHeader',
'showJS.tags',
]
[zh_BO]
ignore = [
'language.direction',
]
[zh_CN]
ignore = [
'lang.dzo',
'lang.iku',
'lang.que',
'language.direction',
]
[zh_TW]
ignore = [
'lang.dzo',
'lang.iku',
'lang.que',
'language.direction',
]

View File

@ -2,6 +2,4 @@ plugins {
// Apply the foojay-resolver plugin to allow automatic download of JDKs
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'Stirling PDF'
include 'stirling-pdf', 'common', 'proprietary'
rootProject.name = 'Stirling-PDF'

View File

@ -1,21 +1,21 @@
package stirling.software.proprietary.security.configuration.ee;
import static stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;
package stirling.software.SPDF.EE;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.EnterpriseEdition;
import stirling.software.common.model.ApplicationProperties.Premium;
import stirling.software.common.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.EE.KeygenLicenseVerifier.License;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition;
import stirling.software.SPDF.model.ApplicationProperties.Premium;
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive;
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class EEAppConfig {
private final ApplicationProperties applicationProperties;
@ -29,23 +29,17 @@ public class EEAppConfig {
migrateEnterpriseSettingsToPremium(this.applicationProperties);
}
@Profile("security")
@Bean(name = "runningProOrHigher")
@Primary
public boolean runningProOrHigher() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL;
}
@Profile("security")
@Bean(name = "license")
@Primary
public String licenseType() {
return licenseKeyChecker.getPremiumLicenseEnabledResult().name();
}
@Profile("security")
@Bean(name = "runningEE")
@Primary
public boolean runningEnterprise() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE;
}
@ -55,9 +49,7 @@ public class EEAppConfig {
return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin();
}
@Profile("security")
@Bean(name = "GoogleDriveEnabled")
@Primary
public boolean googleDriveEnabled() {
return runningProOrHigher()
&& applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled();

View File

@ -1,4 +1,4 @@
package stirling.software.proprietary.security.configuration.ee;
package stirling.software.SPDF.EE;
import java.net.URI;
import java.net.http.HttpClient;
@ -19,15 +19,15 @@ import com.posthog.java.shaded.org.json.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils;
@Service
@Slf4j
@RequiredArgsConstructor
public class KeygenLicenseVerifier {
public enum License {
enum License {
NORMAL,
PRO,
ENTERPRISE
@ -520,7 +520,7 @@ public class KeygenLicenseVerifier {
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.debug("ValidateLicenseResponse body: {}", response.body());
log.info("ValidateLicenseResponse body: {}", response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) {
JsonNode metaNode = jsonResponse.path("meta");
@ -529,9 +529,9 @@ public class KeygenLicenseVerifier {
String detail = metaNode.path("detail").asText();
String code = metaNode.path("code").asText();
log.info("License validity: {}", isValid);
log.info("Validation detail: {}", detail);
log.info("Validation code: {}", code);
log.info("License validity: " + isValid);
log.info("Validation detail: " + detail);
log.info("Validation code: " + code);
// Check if the license itself has floating attribute
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
@ -595,7 +595,7 @@ public class KeygenLicenseVerifier {
.path("isEnterprise")
.asBoolean(false);
log.debug(applicationProperties.toString());
log.info(applicationProperties.toString());
} else {
log.error("Error validating license. Status code: {}", response.statusCode());

View File

@ -1,4 +1,4 @@
package stirling.software.proprietary.security.configuration.ee;
package stirling.software.SPDF.EE;
import java.io.IOException;
import java.nio.file.Files;
@ -10,12 +10,12 @@ import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;
import stirling.software.SPDF.EE.KeygenLicenseVerifier.License;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils;
@Slf4j
@Component
@Slf4j
public class LicenseKeyChecker {
private static final String FILE_PREFIX = "file:";

View File

@ -3,11 +3,11 @@ package stirling.software.SPDF.Factories;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
import stirling.software.common.util.misc.CustomColorReplaceStrategy;
import stirling.software.common.util.misc.InvertFullColorStrategy;
import stirling.software.common.util.misc.ReplaceAndInvertColorStrategy;
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
import stirling.software.SPDF.utils.misc.CustomColorReplaceStrategy;
import stirling.software.SPDF.utils.misc.InvertFullColorStrategy;
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
@Component
public class ReplaceAndInvertColorFactory {

View File

@ -11,6 +11,7 @@ import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
@ -24,37 +25,34 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.common.configuration.AppConfig;
import stirling.software.common.configuration.ConfigInitializer;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.UrlUtils;
import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.UrlUtils;
@Slf4j
@EnableScheduling
@SpringBootApplication(
scanBasePackages = {
"stirling.software.SPDF",
"stirling.software.common",
"stirling.software.proprietary"
})
@SpringBootApplication
public class SPDFApplication {
private static String serverPortStatic;
private static String baseUrlStatic;
private static String contextPathStatic;
private final AppConfig appConfig;
private final Environment env;
private final ApplicationProperties applicationProperties;
private final WebBrowser webBrowser;
@Value("${baseUrl:http://localhost}")
private String baseUrl;
@Value("${server.servlet.context-path:/}")
private String contextPath;
public SPDFApplication(
AppConfig appConfig,
Environment env,
ApplicationProperties applicationProperties,
@Autowired(required = false) WebBrowser webBrowser) {
this.appConfig = appConfig;
this.env = env;
this.applicationProperties = applicationProperties;
this.webBrowser = webBrowser;
@ -143,14 +141,9 @@ public class SPDFApplication {
@PostConstruct
public void init() {
String baseUrl = appConfig.getBaseUrl();
String contextPath = appConfig.getContextPath();
String serverPort = appConfig.getServerPort();
baseUrlStatic = baseUrl;
contextPathStatic = contextPath;
serverPortStatic = serverPort;
baseUrlStatic = this.baseUrl;
contextPathStatic = this.contextPath;
String url = baseUrl + ":" + getStaticPort() + contextPath;
if (webBrowser != null
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
webBrowser.initWebUI(url);
@ -161,7 +154,6 @@ public class SPDFApplication {
try {
String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime();
if (os.contains("win")) {
// For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
@ -178,6 +170,17 @@ public class SPDFApplication {
log.info("Running configs {}", applicationProperties.toString());
}
@Value("${server.port:8080}")
public void setServerPort(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPDFApplication.serverPortStatic =
"0"; // This will let Spring Boot assign an available port
} else {
SPDFApplication.serverPortStatic = port;
}
}
public static void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
@ -202,37 +205,17 @@ public class SPDFApplication {
}
private static String[] getActiveProfile(String[] args) {
// 1. Check for explicitly passed profiles
if (args != null) {
for (String arg : args) {
if (arg.startsWith("--spring.profiles.active=")) {
String[] provided = arg.substring(arg.indexOf('=') + 1).split(",");
if (provided.length > 0) {
log.info("#######0000000000000###############################");
return provided;
}
}
}
}
log.info("######################################");
// 2. Detect if SecurityConfiguration is present on classpath
if (isClassPresent(
"stirling.software.proprietary.security.configuration.SecurityConfiguration")) {
log.info("security");
return new String[] {"security"};
} else {
log.info("default");
if (args == null) {
return new String[] {"default"};
}
}
private static boolean isClassPresent(String className) {
try {
Class.forName(className, false, SPDFApplication.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
for (String arg : args) {
if (arg.contains("spring.profiles.active")) {
return arg.substring(args[0].indexOf('=') + 1).split(", ");
}
}
return new String[] {"default"};
}
public static String getStaticBaseUrl() {

View File

@ -43,8 +43,8 @@ import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.util.UIScaling;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.utils.UIScaling;
@Component
@Slf4j

View File

@ -15,7 +15,7 @@ import io.github.pixee.security.BoundedLineReader;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.UIScaling;
import stirling.software.SPDF.utils.UIScaling;
@Slf4j
public class LoadingWindow extends JDialog {

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import java.io.IOException;
import java.nio.file.Files;
@ -8,45 +8,34 @@ import java.util.List;
import java.util.Locale;
import java.util.Properties;
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.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine;
import stirling.software.common.model.ApplicationProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@Lazy
@Slf4j
@Configuration
@RequiredArgsConstructor
public class AppConfig {
private final Environment env;
private final ApplicationProperties applicationProperties;
@Getter
@Value("${baseUrl:http://localhost}")
private String baseUrl;
@Getter
@Value("${server.servlet.context-path:/}")
private String contextPath;
@Getter
@Value("${server.port:8080}")
private String serverPort;
private final Environment env;
@Bean
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
@ -144,24 +133,10 @@ public class AppConfig {
}
}
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
@Bean(name = "activeSecurity")
public boolean activeSecurity() {
String disableAdditionalFeatures = env.getProperty("DISABLE_ADDITIONAL_FEATURES");
if (disableAdditionalFeatures != null) {
// DISABLE_ADDITIONAL_FEATURES=true means security OFF, so return false
// DISABLE_ADDITIONAL_FEATURES=false means security ON, so return true
return !Boolean.parseBoolean(disableAdditionalFeatures);
}
return env.getProperty("DOCKER_ENABLE_SECURITY", Boolean.class, true);
}
@Bean(name = "missingActiveSecurity")
@ConditionalOnMissingClass(
"stirling.software.proprietary.security.configuration.SecurityConfiguration")
public boolean missingActiveSecurity() {
return true;
return false;
}
@Bean(name = "directoryFilter")
@ -223,60 +198,9 @@ public class AppConfig {
return applicationProperties.getAutomaticallyGenerated().getUUID();
}
@Bean
public ApplicationProperties.Security security() {
return applicationProperties.getSecurity();
}
@Bean
public ApplicationProperties.Security.OAUTH2 oAuth2() {
return applicationProperties.getSecurity().getOauth2();
}
@Bean
public ApplicationProperties.Premium premium() {
return applicationProperties.getPremium();
}
@Bean
public ApplicationProperties.System system() {
return applicationProperties.getSystem();
}
@Bean
public ApplicationProperties.Datasource datasource() {
return applicationProperties.getSystem().getDatasource();
}
@Bean(name = "runningProOrHigher")
@Profile("default")
public boolean runningProOrHigher() {
return false;
}
@Bean(name = "runningEE")
@Profile("default")
public boolean runningEnterprise() {
return false;
}
@Bean(name = "GoogleDriveEnabled")
@Profile("default")
public boolean googleDriveEnabled() {
return false;
}
@Bean(name = "license")
@Profile("default")
public String licenseType() {
return "NORMAL";
}
@Bean(name = "disablePixel")
public boolean disablePixel() {
return Boolean.parseBoolean(env.getProperty("DISABLE_PIXEL", "false"));
return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL"));
}
@Bean(name = "machineType")

View File

@ -5,8 +5,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import stirling.software.common.configuration.interfaces.ShowAdminInterface;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
class AppUpdateService {

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -13,8 +13,6 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.YamlHelper;
/**
* A naive, line-based approach to merging "settings.yml" with "settings.yml.template" while
* preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
@ -78,7 +76,7 @@ public class ConfigInitializer {
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
if (Files.notExists(customSettingsPath)) {
Files.createFile(customSettingsPath);
log.info("Created custom_settings file: {}", customSettingsPath);
log.info("Created custom_settings file: {}", customSettingsPath.toString());
}
}

View File

@ -11,7 +11,7 @@ import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties;
@Service
@Slf4j

View File

@ -1,4 +1,4 @@
package stirling.software.proprietary.security.filter;
package stirling.software.SPDF.config;
import java.io.IOException;

View File

@ -12,8 +12,6 @@ import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.RuntimePathConfig;
@Configuration
@Slf4j
public class ExternalAppDepConfig {

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import java.io.IOException;
import java.io.InputStream;
@ -13,7 +13,7 @@ import org.thymeleaf.templateresource.ITemplateResource;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.InputStreamTemplateResource;
import stirling.software.SPDF.model.InputStreamTemplateResource;
@Slf4j
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {

View File

@ -17,8 +17,8 @@ import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils;
@Component
@Slf4j
@ -59,7 +59,7 @@ public class InitialSetup {
public void initEnableCSRFSecurity() throws IOException {
if (GeneralUtils.isVersionHigher(
"0.46.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
if (!csrf) {
GeneralUtils.saveKeyToSettings("security.csrfDisabled", false);

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import java.io.File;
import java.nio.file.Paths;
@ -48,21 +48,24 @@ public class InstallationPathConfig {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return Paths.get(
System.getenv("APPDATA"), // parent path
"Stirling-PDF")
System.getenv("APPDATA"), // parent path
"Stirling-PDF")
.toString()
+ File.separator;
} else if (os.contains("mac")) {
return Paths.get(
System.getProperty("user.home"),
"Library",
"Application Support",
"Stirling-PDF")
System.getProperty("user.home"),
"Library",
"Application Support",
"Stirling-PDF")
.toString()
+ File.separator;
} else {
return Paths.get(
System.getProperty("user.home"), // parent path
".config",
"Stirling-PDF")
System.getProperty("user.home"), // parent path
".config",
"Stirling-PDF")
.toString()
+ File.separator;
}
}

View File

@ -12,7 +12,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@RequiredArgsConstructor

View File

@ -1,7 +1,5 @@
package stirling.software.SPDF.config;
import stirling.software.common.configuration.InstallationPathConfig;
import ch.qos.logback.core.PropertyDefinerBase;
public class LogbackPropertyLoader extends PropertyDefinerBase {

View File

@ -16,7 +16,7 @@ import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import stirling.software.common.util.RequestUriUtils;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component
@RequiredArgsConstructor

View File

@ -13,7 +13,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@RequiredArgsConstructor

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package stirling.software.common.configuration;
package stirling.software.SPDF.config;
import java.nio.file.Files;
import java.nio.file.Path;
@ -9,9 +9,9 @@ import org.springframework.context.annotation.Configuration;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.CustomPaths.Operations;
import stirling.software.common.model.ApplicationProperties.CustomPaths.Pipeline;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations;
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline;
@Slf4j
@Configuration

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