Compare commits
45 Commits
csrf
...
autoMergeT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d797d700db | ||
|
|
ff1086b0d5 | ||
|
|
eb3c3cace0 | ||
|
|
b2d1f20ebe | ||
|
|
6834067e7a | ||
|
|
d4b120b5f8 | ||
|
|
f0310a4177 | ||
|
|
06a53de350 | ||
|
|
bb99f9ff83 | ||
|
|
72b7b1b838 | ||
|
|
cbaea9cca9 | ||
|
|
5605e4d3bb | ||
|
|
bf0958f588 | ||
|
|
0ae862a84a | ||
|
|
3e216872ce | ||
|
|
1b26fa40f2 | ||
|
|
672389d6b8 | ||
|
|
888ef104a2 | ||
|
|
f90db3cff7 | ||
|
|
e0f553c15a | ||
|
|
89d319332a | ||
|
|
225e797176 | ||
|
|
5e28023853 | ||
|
|
4ef118c4eb | ||
|
|
92401b9e7f | ||
|
|
c78f924522 | ||
|
|
630f31c3f8 | ||
|
|
7dca87b377 | ||
|
|
8619b1cf59 | ||
|
|
c6c6cbeaa9 | ||
|
|
c17b4e29ff | ||
|
|
d2d8e2ef42 | ||
|
|
62e96e3f94 | ||
|
|
f95eea07fb | ||
|
|
1bd7e420e5 | ||
|
|
76cbf94fdc | ||
|
|
b2da426cc1 | ||
|
|
cce4693aea | ||
|
|
1c82523dc5 | ||
|
|
2241e2c1f2 | ||
|
|
c11076ee94 | ||
|
|
9273b163a8 | ||
|
|
5e0adc6234 | ||
|
|
e5f39b79f7 | ||
|
|
b98f8627ac |
1
.github/scripts/requirements_pre_commit.in
vendored
Normal file
1
.github/scripts/requirements_pre_commit.in
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pre-commit
|
||||||
93
.github/scripts/requirements_pre_commit.txt
vendored
Normal file
93
.github/scripts/requirements_pre_commit.txt
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_pre_commit.txt' '.github\scripts\requirements_pre_commit.in'
|
||||||
|
#
|
||||||
|
cfgv==3.4.0 \
|
||||||
|
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||||
|
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||||
|
# via pre-commit
|
||||||
|
distlib==0.3.9 \
|
||||||
|
--hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \
|
||||||
|
--hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403
|
||||||
|
# via virtualenv
|
||||||
|
filelock==3.16.1 \
|
||||||
|
--hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \
|
||||||
|
--hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435
|
||||||
|
# via virtualenv
|
||||||
|
identify==2.6.5 \
|
||||||
|
--hash=sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566 \
|
||||||
|
--hash=sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc
|
||||||
|
# via pre-commit
|
||||||
|
nodeenv==1.9.1 \
|
||||||
|
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||||
|
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||||
|
# via pre-commit
|
||||||
|
platformdirs==4.3.6 \
|
||||||
|
--hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \
|
||||||
|
--hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb
|
||||||
|
# via virtualenv
|
||||||
|
pre-commit==4.0.1 \
|
||||||
|
--hash=sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2 \
|
||||||
|
--hash=sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878
|
||||||
|
# via -r .github\scripts\requirements_pre_commit.in
|
||||||
|
pyyaml==6.0.2 \
|
||||||
|
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||||
|
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||||
|
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
|
||||||
|
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
|
||||||
|
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||||
|
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||||
|
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||||
|
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||||
|
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||||
|
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
|
||||||
|
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
|
||||||
|
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
|
||||||
|
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
|
||||||
|
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
|
||||||
|
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||||
|
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
|
||||||
|
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||||
|
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
|
||||||
|
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||||
|
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||||
|
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||||
|
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
|
||||||
|
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
|
||||||
|
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||||
|
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||||
|
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||||
|
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
|
||||||
|
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||||
|
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||||
|
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
|
||||||
|
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||||
|
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
|
||||||
|
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||||
|
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
|
||||||
|
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
|
||||||
|
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||||
|
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
|
||||||
|
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
|
||||||
|
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
|
||||||
|
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||||
|
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||||
|
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||||
|
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||||
|
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||||
|
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
|
||||||
|
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
|
||||||
|
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
|
||||||
|
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||||
|
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
|
||||||
|
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||||
|
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||||
|
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||||
|
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||||
|
# via pre-commit
|
||||||
|
virtualenv==20.28.1 \
|
||||||
|
--hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \
|
||||||
|
--hash=sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329
|
||||||
|
# via pre-commit
|
||||||
1
.github/scripts/requirements_sync_readme.in
vendored
Normal file
1
.github/scripts/requirements_sync_readme.in
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tomlkit
|
||||||
10
.github/scripts/requirements_sync_readme.txt
vendored
Normal file
10
.github/scripts/requirements_sync_readme.txt
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_sync_readme.txt' '.github\scripts\requirements_sync_readme.in'
|
||||||
|
#
|
||||||
|
tomlkit==0.13.2 \
|
||||||
|
--hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \
|
||||||
|
--hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79
|
||||||
|
# via -r .github\scripts\requirements_sync_readme.in
|
||||||
10
.github/workflows/PR-Demo-Comment.yml
vendored
10
.github/workflows/PR-Demo-Comment.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -95,8 +95,8 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: "17"
|
||||||
distribution: 'temurin'
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Run Gradle Command
|
- name: Run Gradle Command
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Build and push PR-specific image
|
- name: Build and push PR-specific image
|
||||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
6
.github/workflows/PR-Demo-cleanup.yml
vendored
6
.github/workflows/PR-Demo-cleanup.yml
vendored
@@ -8,8 +8,8 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
|
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
|
||||||
CLEANUP_PERFORMED: 'false' # Add flag to track if cleanup occurred
|
CLEANUP_PERFORMED: "false" # Add flag to track if cleanup occurred
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/auto-labeler.yml
vendored
2
.github/workflows/auto-labeler.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}
|
||||||
path: |
|
path: |
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -105,9 +105,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Pip requirements
|
- name: Pip requirements
|
||||||
run: |
|
run: |
|
||||||
pip install --require-hashes -r ./cucumber/requirements.txt
|
pip install --require-hashes -r ./cucumber/requirements.txt
|
||||||
|
|
||||||
- name: Run Docker Compose Tests
|
- name: Run Docker Compose Tests
|
||||||
run: |
|
run: |
|
||||||
|
chmod +x ./cucumber/test_webpages.sh
|
||||||
chmod +x ./test.sh
|
chmod +x ./test.sh
|
||||||
./test.sh
|
./test.sh
|
||||||
|
|||||||
2
.github/workflows/check_properties.yml
vendored
2
.github/workflows/check_properties.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/codeql.yml-disabled
vendored
2
.github/workflows/codeql.yml-disabled
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/dependency-review.yml
vendored
8
.github/workflows/dependency-review.yml
vendored
@@ -6,7 +6,7 @@
|
|||||||
# PRs introducing known-vulnerable packages will be blocked from merging.
|
# PRs introducing known-vulnerable packages will be blocked from merging.
|
||||||
#
|
#
|
||||||
# Source repository: https://github.com/actions/dependency-review-action
|
# Source repository: https://github.com/actions/dependency-review-action
|
||||||
name: 'Dependency Review'
|
name: "Dependency Review"
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -17,11 +17,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: 'Checkout Repository'
|
- name: "Checkout Repository"
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: 'Dependency Review'
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
|
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
|
||||||
|
|||||||
37
.github/workflows/licenses-update.yml
vendored
37
.github/workflows/licenses-update.yml
vendored
@@ -18,10 +18,17 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Generate GitHub App Token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ vars.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
@@ -42,8 +49,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up git config
|
- name: Set up git config
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "github-actions[bot]"
|
git config --global user.name "stirlingbot[bot]"
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "1113334+stirlingbot[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
- name: Run git add
|
- name: Run git add
|
||||||
run: |
|
run: |
|
||||||
@@ -55,32 +62,22 @@ jobs:
|
|||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: "Update 3rd Party Licenses"
|
commit-message: "Update 3rd Party Licenses"
|
||||||
committer: GitHub Action <action@github.com>
|
committer: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
|
||||||
author: GitHub Action <action@github.com>
|
author: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: update-3rd-party-licenses
|
branch: update-3rd-party-licenses
|
||||||
title: "Update 3rd Party Licenses"
|
title: "Update 3rd Party Licenses"
|
||||||
body: |
|
body: |
|
||||||
Auto-generated by [create-pull-request][1]
|
Auto-generated by StirlingBot
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
|
||||||
labels: licenses,github-actions
|
labels: licenses,github-actions
|
||||||
draft: false
|
draft: false
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
sign-commits: true
|
sign-commits: true
|
||||||
|
|
||||||
- name: Auto approve
|
- name: Enable Pull Request Automerge
|
||||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||||
run: gh pr review --approve "${{ steps.cpr.outputs.pull-request-number }}"
|
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Enable auto-merge
|
|
||||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
|
||||||
uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3.0.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
|
||||||
merge-method: squash # Choose the merge method: merge, squash, or rebase
|
|
||||||
|
|||||||
2
.github/workflows/manage-label.yml
vendored
2
.github/workflows/manage-label.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
260
.github/workflows/multiOSReleases.yml
vendored
260
.github/workflows/multiOSReleases.yml
vendored
@@ -9,27 +9,137 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
read_versions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
|
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
# Get version number
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get version number mac
|
||||||
|
id: versionNumberMac
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
CURRENT_YEAR=$(date +'%Y')
|
||||||
|
IFS='.' read -r -a VERSION_PARTS <<< "$VERSION"
|
||||||
|
MAC_VERSION="$CURRENT_YEAR.${VERSION_PARTS[1]:-0}.${VERSION_PARTS[2]:-0}"
|
||||||
|
echo "versionNumberMac=$MAC_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
build-portable:
|
||||||
|
needs: read_versions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
enable_security: [true, false]
|
||||||
|
include:
|
||||||
|
- enable_security: true
|
||||||
|
file_suffix: "-with-login"
|
||||||
|
- enable_security: false
|
||||||
|
file_suffix: ""
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
|
with:
|
||||||
|
java-version: "21"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
|
with:
|
||||||
|
gradle-version: 8.12
|
||||||
|
|
||||||
|
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||||
|
run: ./gradlew clean createExe
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||||
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
|
- name: Rename binaries
|
||||||
|
run: |
|
||||||
|
mkdir ./binaries
|
||||||
|
mv ./build/launch4j/Stirling-PDF.exe ./binaries/win-Stirling-PDF-portable-Server${{ matrix.file_suffix }}.exe
|
||||||
|
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
with:
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
name: stirling${{ matrix.file_suffix }}-binaries
|
||||||
|
path: |
|
||||||
|
./binaries/*
|
||||||
|
|
||||||
|
sign_verify-portable:
|
||||||
|
needs: [build-portable, read_versions]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
enable_security: [true, false]
|
||||||
|
include:
|
||||||
|
- enable_security: true
|
||||||
|
file_suffix: "with-login-"
|
||||||
|
- enable_security: false
|
||||||
|
file_suffix: ""
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: stirling-${{ matrix.file_suffix }}binaries
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Upload signed artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
with:
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
name: stirling-${{ matrix.file_suffix }}signed
|
||||||
|
path: |
|
||||||
|
./*
|
||||||
|
!cosign.*
|
||||||
|
|
||||||
build-installers:
|
build-installers:
|
||||||
|
needs: read_versions
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
platform: win
|
platform: win-
|
||||||
ext: exe
|
# - os: macos-latest
|
||||||
#- os: macos-latest
|
# platform: mac-
|
||||||
# platform: mac
|
# - os: ubuntu-latest
|
||||||
# ext: dmg
|
# platform: linux-
|
||||||
#- os: ubuntu-latest
|
|
||||||
# platform: linux
|
|
||||||
# ext: deb
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -52,24 +162,6 @@ jobs:
|
|||||||
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
|
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
|
||||||
.\wix.exe /install /quiet
|
.\wix.exe /install /quiet
|
||||||
|
|
||||||
# Install Linux dependencies
|
|
||||||
- name: Install Linux Dependencies
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y fakeroot rpm
|
|
||||||
|
|
||||||
# Get version number
|
|
||||||
- name: Get version number
|
|
||||||
id: versionNumber
|
|
||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Get version number mac
|
|
||||||
id: versionNumberMac
|
|
||||||
run: echo "versionNumberMac=$(./gradlew printMacVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
# Build installer
|
# Build installer
|
||||||
- name: Build Installer
|
- name: Build Installer
|
||||||
run: ./gradlew build jpackage -x test --info
|
run: ./gradlew build jpackage -x test --info
|
||||||
@@ -82,24 +174,114 @@ jobs:
|
|||||||
id: prepare
|
id: prepare
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
mkdir ./binaries
|
||||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}"
|
mv "./build/jpackage/Stirling-PDF-${{ needs.read_versions.outputs.version }}.exe" "./binaries/Stirling-PDF-win-installer.exe"
|
||||||
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
||||||
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
mv "./build/jpackage/Stirling-PDF-${{ needs.read_versions.outputs.versionMac }}.dmg" "./binaries/Stirling-PDF-mac-installer.dmg"
|
||||||
else
|
else
|
||||||
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
mv "./build/jpackage/stirling-pdf_${{ needs.read_versions.outputs.version }}-1_amd64.deb" "./binaries/Stirling-PDF-linux-installer.deb"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload installer as artifact for testing
|
- name: Display structure of downloaded files
|
||||||
- name: Upload Installer Artifact
|
run: ls -R ./binaries
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
|
||||||
path: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
name: ${{ matrix.platform }}binaries
|
||||||
|
path: |
|
||||||
|
./binaries/*
|
||||||
|
|
||||||
- name: Upload binaries to release
|
sign_verify:
|
||||||
|
needs: [read_versions, build-installers]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win-
|
||||||
|
# - os: macos-latest
|
||||||
|
# platform: mac-
|
||||||
|
# - os: ubuntu-latest
|
||||||
|
# platform: linux-
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.platform }}binaries
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Install Cosign
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
|
|
||||||
|
- name: Generate key pair
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: cosign generate-key-pair
|
||||||
|
|
||||||
|
- name: Sign and generate attestations
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
cosign sign-blob \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-signature ./Stirling-PDF-win-installer.exe.sig \
|
||||||
|
./Stirling-PDF-win-installer.exe
|
||||||
|
|
||||||
|
cosign attest-blob \
|
||||||
|
--predicate - \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-attestation ./Stirling-PDF-win-installer.exe.intoto.jsonl \
|
||||||
|
./Stirling-PDF-win-installer.exe
|
||||||
|
|
||||||
|
cosign verify-blob \
|
||||||
|
--key ./cosign.pub \
|
||||||
|
--signature ./Stirling-PDF-win-installer.exe.sig \
|
||||||
|
./Stirling-PDF-win-installer.exe
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Upload signed artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
with:
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
name: ${{ matrix.platform }}signed
|
||||||
|
path: |
|
||||||
|
./Stirling-PDF-${{ matrix.platform }}installer.*
|
||||||
|
!cosign.*
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: [read_versions, sign_verify, sign_verify-portable]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Download signed artifacts
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
||||||
with:
|
with:
|
||||||
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
tag_name: v${{ needs.read_versions.outputs.version }}
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
./*signed/*
|
||||||
|
|||||||
14
.github/workflows/pre_commit.yml
vendored
14
.github/workflows/pre_commit.yml
vendored
@@ -1,20 +1,24 @@
|
|||||||
name: Pre-commit
|
name: Pre-commit
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
pre-commit:
|
||||||
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
@@ -24,7 +28,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
- name: Run Pre-Commit Hooks
|
- name: Run Pre-Commit Hooks
|
||||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
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
|
continue-on-error: true
|
||||||
- name: Set up git config
|
- name: Set up git config
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
12
.github/workflows/push-docker.yml
vendored
12
.github/workflows/push-docker.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
with:
|
with:
|
||||||
cosign-release: 'v2.4.1'
|
cosign-release: "v2.4.1"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
password: ${{ github.token }}
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||||
|
|
||||||
- name: Convert repository owner to lowercase
|
- name: Convert repository owner to lowercase
|
||||||
id: repoowner
|
id: repoowner
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile
|
- name: Build and push main Dockerfile
|
||||||
id: build-push-regular
|
id: build-push-regular
|
||||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push Dockerfile-ultra-lite
|
- name: Build and push Dockerfile-ultra-lite
|
||||||
id: build-push-lite
|
id: build-push-lite
|
||||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
@@ -165,7 +165,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile fat
|
- name: Build and push main Dockerfile fat
|
||||||
id: build-push-fat
|
id: build-push-fat
|
||||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
|||||||
163
.github/workflows/releaseArtifacts.yml
vendored
163
.github/workflows/releaseArtifacts.yml
vendored
@@ -9,11 +9,8 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
enable_security: [true, false]
|
||||||
@@ -22,9 +19,11 @@ jobs:
|
|||||||
file_suffix: "-with-login"
|
file_suffix: "-with-login"
|
||||||
- enable_security: false
|
- enable_security: false
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -48,38 +47,134 @@ jobs:
|
|||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename binarie
|
- name: Rename binaries
|
||||||
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
run: |
|
||||||
|
mv ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
mv ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload Assets binarie
|
- name: Debug build artifacts
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
run: |
|
||||||
|
echo "Current Directory: $(pwd)"
|
||||||
|
ls -R ./build/libs
|
||||||
|
ls -R ./build/launch4j
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
name: binaries${{ matrix.file_suffix }}
|
||||||
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
path: |
|
||||||
overwrite: true
|
./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.*
|
||||||
retention-days: 1
|
./build/libs/Stirling-PDF${{ matrix.file_suffix }}.*
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload binaries to release
|
sign_verify:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
enable_security: [true, false]
|
||||||
|
include:
|
||||||
|
- enable_security: true
|
||||||
|
file_suffix: "-with-login"
|
||||||
|
- enable_security: false
|
||||||
|
file_suffix: ""
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: binaries${{ matrix.file_suffix }}
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Install Cosign
|
||||||
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
|
|
||||||
|
- name: Generate key pair
|
||||||
|
run: cosign generate-key-pair
|
||||||
|
|
||||||
|
- name: Sign and generate attestations
|
||||||
|
run: |
|
||||||
|
cosign sign-blob \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-signature ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.sig \
|
||||||
|
./libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
cosign attest-blob \
|
||||||
|
--predicate - \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-attestation ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.intoto.jsonl \
|
||||||
|
./libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
cosign verify-blob \
|
||||||
|
--key ./cosign.pub \
|
||||||
|
--signature ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.sig \
|
||||||
|
./libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
cosign sign-blob \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-signature ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.sig \
|
||||||
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
|
cosign attest-blob \
|
||||||
|
--predicate - \
|
||||||
|
--key ./cosign.key \
|
||||||
|
--yes \
|
||||||
|
--output-attestation ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.intoto.jsonl \
|
||||||
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
|
cosign verify-blob \
|
||||||
|
--key ./cosign.pub \
|
||||||
|
--signature ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.sig \
|
||||||
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
|
- name: Upload signed artifacts
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
with:
|
||||||
|
name: signed${{ matrix.file_suffix }}
|
||||||
|
path: |
|
||||||
|
./libs/Stirling-PDF${{ matrix.file_suffix }}.*
|
||||||
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.*
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: [build, sign_verify]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
enable_security: [true, false]
|
||||||
|
include:
|
||||||
|
- enable_security: true
|
||||||
|
file_suffix: "-with-login"
|
||||||
|
- enable_security: false
|
||||||
|
file_suffix: ""
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Download signed artifacts
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: signed${{ matrix.file_suffix }}
|
||||||
|
|
||||||
|
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
||||||
with:
|
with:
|
||||||
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
tag_name: v${{ needs.build.outputs.version }}
|
||||||
|
generate_release_notes: true
|
||||||
- name: Rename jar binaries
|
files: |
|
||||||
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
./libs/Stirling-PDF*
|
||||||
|
./launch4j/Stirling-PDF-Server*
|
||||||
- name: Upload Assets jar binaries
|
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
|
||||||
with:
|
|
||||||
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
|
||||||
name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
|
||||||
overwrite: true
|
|
||||||
retention-days: 1
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload jar binaries to release
|
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
|
||||||
with:
|
|
||||||
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
|
||||||
|
|||||||
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
# To guarantee Maintained check is occasionally updated. See
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '20 7 * * 2'
|
- cron: "20 7 * * 2"
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@@ -74,6 +74,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -37,4 +37,4 @@ jobs:
|
|||||||
only-issue-labels: "more-info-needed"
|
only-issue-labels: "more-info-needed"
|
||||||
days-before-pr-stale: -1 # Prevents PRs from being marked as stale
|
days-before-pr-stale: -1 # Prevents PRs from being marked as stale
|
||||||
days-before-pr-close: -1 # Prevents PRs from being closed
|
days-before-pr-close: -1 # Prevents PRs from being closed
|
||||||
start-date: '2024-07-06T00:00:00Z' # ISO 8601 Format
|
start-date: "2024-07-06T00:00:00Z" # ISO 8601 Format
|
||||||
|
|||||||
2
.github/workflows/swagger.yml
vendored
2
.github/workflows/swagger.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/sync_files.yml
vendored
4
.github/workflows/sync_files.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install tomlkit
|
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||||
- name: Sync README
|
- name: Sync README
|
||||||
run: python scripts/counter_translation.py
|
run: python scripts/counter_translation.py
|
||||||
- name: Set up git config
|
- name: Set up git config
|
||||||
|
|||||||
154
.github/workflows/testdriver.yml
vendored
Normal file
154
.github/workflows/testdriver.yml
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
name: UI test with TestDriverAI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master", "UITest", "testdriver"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew clean build
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
|
- name: Build and push test image
|
||||||
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
|
||||||
|
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Deploy to VPS
|
||||||
|
run: |
|
||||||
|
cat > docker-compose.yml << EOF
|
||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: stirling-pdf-test-${{ github.sha }}
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
|
||||||
|
ports:
|
||||||
|
- "1337:8080"
|
||||||
|
volumes:
|
||||||
|
- /stirling/test-${{ github.sha }}/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/test-${{ github.sha }}/config:/configs:rw
|
||||||
|
- /stirling/test-${{ github.sha }}/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
|
UI_APPNAME: "Stirling-PDF Test"
|
||||||
|
UI_HOMEDESCRIPTION: "Test Deployment"
|
||||||
|
UI_APPNAMENAVBAR: "Test"
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||||
|
SYSTEM_ENABLEANALYTICS: "false"
|
||||||
|
restart: on-failure:5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose.yml
|
||||||
|
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << EOF
|
||||||
|
mkdir -p /stirling/test-${{ github.sha }}/{data,config,logs}
|
||||||
|
mv /tmp/docker-compose.yml /stirling/test-${{ github.sha }}/docker-compose.yml
|
||||||
|
cd /stirling/test-${{ github.sha }}
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Run TestDriver.ai
|
||||||
|
uses: testdriverai/action@47e87c5d50beeeb3da624b2d9b5c1391269d6d22 #1.0.0
|
||||||
|
with:
|
||||||
|
key: ${{secrets.TESTDRIVER_API_KEY}}
|
||||||
|
prerun: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm install dashcam-chrome --save
|
||||||
|
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=$(pwd)/node_modules/dashcam-chrome/build", "http://${{ secrets.VPS_HOST }}:1337"
|
||||||
|
Start-Sleep -Seconds 20
|
||||||
|
prompt: |
|
||||||
|
1. /run testdriver/test.yml
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
FORCE_COLOR: "3"
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
needs: [deploy, test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Cleanup deployment
|
||||||
|
run: |
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << EOF
|
||||||
|
cd /stirling/test-${{ github.sha }}
|
||||||
|
docker-compose down
|
||||||
|
cd /stirling
|
||||||
|
rm -rf test-${{ github.sha }}
|
||||||
|
EOF
|
||||||
2
.github/workflows/update-translations.yml
vendored
2
.github/workflows/update-translations.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -14,12 +14,16 @@ local.properties
|
|||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
version.properties
|
version.properties
|
||||||
|
|
||||||
|
#### Stirling-PDF Files ###
|
||||||
pipeline/watchedFolders/
|
pipeline/watchedFolders/
|
||||||
pipeline/finishedFolders/
|
pipeline/finishedFolders/
|
||||||
#### Stirling-PDF Files ###
|
|
||||||
customFiles/
|
customFiles/
|
||||||
configs/
|
configs/
|
||||||
watchedFolders/
|
watchedFolders/
|
||||||
|
!cucumber/
|
||||||
|
!cucumber/exampleFiles/
|
||||||
|
!cucumber/exampleFiles/example_html.zip
|
||||||
|
|
||||||
# Gradle
|
# Gradle
|
||||||
.gradle
|
.gradle
|
||||||
@@ -111,6 +115,7 @@ watchedFolders/
|
|||||||
*.war
|
*.war
|
||||||
*.nar
|
*.nar
|
||||||
*.ear
|
*.ear
|
||||||
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ repos:
|
|||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
files: \.(properties|html|css|js|py|md)$
|
files: \.(properties|html|css|js|py|md)$
|
||||||
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile)
|
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.22.0
|
rev: v8.22.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.20.3
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
|
|||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.20.3
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.21.0@sha256:21dc6063fd678b478f57c0e13f47560d0ea4eeba26dfc947b2a4f81f686b9f45
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -120,10 +120,10 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
@@ -132,8 +132,8 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
@@ -141,8 +141,8 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
@@ -152,7 +152,7 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|||||||
45
USERS.md
Normal file
45
USERS.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Who is using Stirling-PDF?
|
||||||
|
|
||||||
|
Understanding the diverse applications of Stirling-PDF can be an invaluable resource for collaboration and learning. This page provides a directory of users and use cases. If you are using Stirling-PDF, consider sharing your experiences to help others and foster a community of best practices.
|
||||||
|
|
||||||
|
## Adding Yourself as a User
|
||||||
|
|
||||||
|
If you're using Stirling-PDF or have integrated it into your platform or workflow, please consider contributing to this list by describing your use case. You can do this by opening a pull request to this file and adding your details in the format below:
|
||||||
|
|
||||||
|
- **N**: Name of the organization or individual.
|
||||||
|
- **D**: A brief description of your usage.
|
||||||
|
- **U**: Specific features or capabilities utilized.
|
||||||
|
- **L**: Optional link for further information (e.g., website, blog post).
|
||||||
|
- **Q**: Contact information for sharing insights (optional).
|
||||||
|
|
||||||
|
Example entry:
|
||||||
|
|
||||||
|
```
|
||||||
|
* N: Example Corp
|
||||||
|
D: Using Stirling-PDF for automated document processing in our SaaS platform focusing on compression.
|
||||||
|
U: OCR, merging PDFs, metadata editing, encryption, compression.
|
||||||
|
L: https://example.com/stirling-pdf
|
||||||
|
Q: @example-user on Discord/email
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements for Listing
|
||||||
|
|
||||||
|
- You must represent the entity you're listing and ensure the details are accurate.
|
||||||
|
- Trial deployments are welcome if they represent a realistic evaluation of Stirling-PDF in action.
|
||||||
|
- Community contributions, including home-lab setups or non-commercial uses, are encouraged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Users (Alphabetically)
|
||||||
|
|
||||||
|
* N:
|
||||||
|
D:
|
||||||
|
U:
|
||||||
|
L:
|
||||||
|
|
||||||
|
* N:
|
||||||
|
D:
|
||||||
|
U:
|
||||||
|
L:
|
||||||
10
build.gradle
10
build.gradle
@@ -5,7 +5,7 @@ plugins {
|
|||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
id "edu.sc.seis.launch4j" version "3.0.6"
|
id "edu.sc.seis.launch4j" version "3.0.6"
|
||||||
id "com.diffplug.spotless" version "6.25.0"
|
id "com.diffplug.spotless" version "7.0.1"
|
||||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||||
@@ -27,7 +27,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.36.6"
|
version = "0.37.0"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -372,7 +372,7 @@ dependencies {
|
|||||||
//general PDF
|
//general PDF
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
||||||
implementation ("com.opencsv:opencsv:5.9") {
|
implementation ("com.opencsv:opencsv:5.10") {
|
||||||
exclude group: "commons-logging", module: "commons-logging"
|
exclude group: "commons-logging", module: "commons-logging"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +396,7 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||||
implementation "io.micrometer:micrometer-core:1.14.2"
|
implementation "io.micrometer:micrometer-core:1.14.3"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.24.0"
|
implementation "org.commonmark:commonmark:0.24.0"
|
||||||
@@ -405,6 +405,8 @@ dependencies {
|
|||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|
||||||
|
implementation 'org.jsoup:jsoup:1.18.3'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
|||||||
@@ -8,4 +8,3 @@
|
|||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ def after_scenario(context, scenario):
|
|||||||
# Remove any temporary files
|
# Remove any temporary files
|
||||||
for temp_file in os.listdir('.'):
|
for temp_file in os.listdir('.'):
|
||||||
if temp_file.startswith('genericNonCustomisableName') or temp_file.startswith('temp_image_'):
|
if temp_file.startswith('genericNonCustomisableName') or temp_file.startswith('temp_image_'):
|
||||||
os.remove(temp_file)
|
os.remove(temp_file)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
from behave import given, when, then
|
from behave import given, when, then
|
||||||
from PyPDF2 import PdfWriter, PdfReader
|
from pypdf import PdfWriter, PdfReader
|
||||||
|
from pypdf.errors import PdfReadError
|
||||||
import io
|
import io
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
@@ -42,7 +43,7 @@ def step_use_example_file(context, filePath, fileInput):
|
|||||||
context.file_name = filePath.split('/')[-1]
|
context.file_name = filePath.split('/')[-1]
|
||||||
if not hasattr(context, 'files'):
|
if not hasattr(context, 'files'):
|
||||||
context.files = {}
|
context.files = {}
|
||||||
|
|
||||||
# Ensure the file exists before opening
|
# Ensure the file exists before opening
|
||||||
try:
|
try:
|
||||||
example_file = open(filePath, 'rb')
|
example_file = open(filePath, 'rb')
|
||||||
@@ -165,17 +166,17 @@ def step_pdf_contains_pages_with_random_text(context, page_count):
|
|||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
c = canvas.Canvas(buffer, pagesize=letter)
|
c = canvas.Canvas(buffer, pagesize=letter)
|
||||||
width, height = letter
|
width, height = letter
|
||||||
|
|
||||||
for _ in range(page_count):
|
for _ in range(page_count):
|
||||||
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
|
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
|
||||||
c.drawString(100, height - 100, text)
|
c.drawString(100, height - 100, text)
|
||||||
c.showPage()
|
c.showPage()
|
||||||
|
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
with open(context.file_name, 'wb') as f:
|
with open(context.file_name, 'wb') as f:
|
||||||
f.write(buffer.getvalue())
|
f.write(buffer.getvalue())
|
||||||
|
|
||||||
context.files[context.param_name].close()
|
context.files[context.param_name].close()
|
||||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
@@ -184,16 +185,16 @@ def step_pdf_pages_contain_text(context, text):
|
|||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
c = canvas.Canvas(buffer, pagesize=letter)
|
c = canvas.Canvas(buffer, pagesize=letter)
|
||||||
width, height = letter
|
width, height = letter
|
||||||
|
|
||||||
for _ in range(len(PdfReader(context.file_name).pages)):
|
for _ in range(len(PdfReader(context.file_name).pages)):
|
||||||
c.drawString(100, height - 100, text)
|
c.drawString(100, height - 100, text)
|
||||||
c.showPage()
|
c.showPage()
|
||||||
|
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
with open(context.file_name, 'wb') as f:
|
with open(context.file_name, 'wb') as f:
|
||||||
f.write(buffer.getvalue())
|
f.write(buffer.getvalue())
|
||||||
|
|
||||||
context.files[context.param_name].close()
|
context.files[context.param_name].close()
|
||||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
@@ -345,7 +346,7 @@ def step_check_response_pdf_page_count(context, page_count):
|
|||||||
def step_check_response_zip_file_count(context, file_count):
|
def step_check_response_zip_file_count(context, file_count):
|
||||||
response_file = io.BytesIO(context.response.content)
|
response_file = io.BytesIO(context.response.content)
|
||||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||||
actual_file_count = len(zip_file.namelist())
|
actual_file_count = len(zip_file.namelist())
|
||||||
assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files"
|
assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files"
|
||||||
|
|
||||||
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
|
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
|
||||||
@@ -354,7 +355,7 @@ def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc):
|
|||||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||||
actual_doc_count = len(zip_file.namelist())
|
actual_doc_count = len(zip_file.namelist())
|
||||||
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
|
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
|
||||||
|
|
||||||
for file_name in zip_file.namelist():
|
for file_name in zip_file.namelist():
|
||||||
with zip_file.open(file_name) as pdf_file:
|
with zip_file.open(file_name) as pdf_file:
|
||||||
reader = PdfReader(pdf_file)
|
reader = PdfReader(pdf_file)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
behave
|
behave
|
||||||
requests
|
requests
|
||||||
PyPDF2
|
pypdf
|
||||||
reportlab
|
reportlab
|
||||||
PyCryptodome
|
PyCryptodome
|
||||||
|
|||||||
@@ -231,9 +231,9 @@ pycryptodome==3.21.0 \
|
|||||||
--hash=sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297 \
|
--hash=sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297 \
|
||||||
--hash=sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58
|
--hash=sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58
|
||||||
# via -r cucumber\requirements.in
|
# via -r cucumber\requirements.in
|
||||||
pypdf2==3.0.1 \
|
pypdf==5.1.0 \
|
||||||
--hash=sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440 \
|
--hash=sha256:3bd4f503f4ebc58bae40d81e81a9176c400cbbac2ba2d877367595fb524dfdfc \
|
||||||
--hash=sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928
|
--hash=sha256:425a129abb1614183fd1aca6982f650b47f8026867c0ce7c4b9f281c443d2740
|
||||||
# via -r cucumber\requirements.in
|
# via -r cucumber\requirements.in
|
||||||
reportlab==4.2.5 \
|
reportlab==4.2.5 \
|
||||||
--hash=sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f \
|
--hash=sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f \
|
||||||
@@ -249,6 +249,10 @@ six==1.17.0 \
|
|||||||
# via
|
# via
|
||||||
# behave
|
# behave
|
||||||
# parse-type
|
# parse-type
|
||||||
|
typing-extensions==4.12.2 \
|
||||||
|
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||||
|
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||||
|
# via pypdf
|
||||||
urllib3==2.3.0 \
|
urllib3==2.3.0 \
|
||||||
--hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
|
--hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
|
||||||
--hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
|
--hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
|
||||||
|
|||||||
97
cucumber/test_webpages.sh
Normal file
97
cucumber/test_webpages.sh
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Function to check a single webpage
|
||||||
|
check_webpage() {
|
||||||
|
local url=$1
|
||||||
|
local base_url=${2:-"http://localhost:8080"}
|
||||||
|
local full_url="${base_url}${url}"
|
||||||
|
local timeout=10
|
||||||
|
|
||||||
|
echo -n "Testing $full_url ... "
|
||||||
|
|
||||||
|
# Use curl to fetch the page with timeout
|
||||||
|
response=$(curl -s -w "\n%{http_code}" --max-time $timeout "$full_url")
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "FAILED - Connection error or timeout"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split response into body and status code
|
||||||
|
HTTP_STATUS=$(echo "$response" | tail -n1)
|
||||||
|
BODY=$(echo "$response" | sed '$d')
|
||||||
|
|
||||||
|
# Check HTTP status
|
||||||
|
if [ "$HTTP_STATUS" != "200" ]; then
|
||||||
|
echo "FAILED - HTTP Status: $HTTP_STATUS"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if response contains HTML
|
||||||
|
if ! echo "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then
|
||||||
|
echo "FAILED - Response is not HTML"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function to test all URLs from the list
|
||||||
|
test_all_urls() {
|
||||||
|
local url_file=$1
|
||||||
|
local base_url=${2:-"http://localhost:8080"}
|
||||||
|
local failed_count=0
|
||||||
|
local total_count=0
|
||||||
|
local start_time=$(date +%s)
|
||||||
|
|
||||||
|
echo "Starting webpage tests..."
|
||||||
|
echo "Base URL: $base_url"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
while IFS= read -r url || [ -n "$url" ]; do
|
||||||
|
# Skip empty lines
|
||||||
|
[ -z "$url" ] && continue
|
||||||
|
|
||||||
|
((total_count++))
|
||||||
|
if ! check_webpage "$url" "$base_url"; then
|
||||||
|
((failed_count++))
|
||||||
|
fi
|
||||||
|
done < "$url_file"
|
||||||
|
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - start_time))
|
||||||
|
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "Test Summary:"
|
||||||
|
echo "Total tests: $total_count"
|
||||||
|
echo "Failed tests: $failed_count"
|
||||||
|
echo "Passed tests: $((total_count - failed_count))"
|
||||||
|
echo "Duration: ${duration} seconds"
|
||||||
|
|
||||||
|
return $failed_count
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
local url_file="${script_dir}/webpage_urls.txt"
|
||||||
|
|
||||||
|
if [ ! -f "$url_file" ]; then
|
||||||
|
echo "Error: URL list file not found: $url_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run tests using the URL list
|
||||||
|
if test_all_urls "$url_file"; then
|
||||||
|
echo "All webpage tests passed!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Some webpage tests failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main if script is executed directly
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
54
cucumber/webpage_urls.txt
Normal file
54
cucumber/webpage_urls.txt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
/
|
||||||
|
/multi-tool
|
||||||
|
/merge-pdfs
|
||||||
|
/split-pdfs
|
||||||
|
/rotate-pdf
|
||||||
|
/remove-pages
|
||||||
|
/pdf-organizer
|
||||||
|
/multi-page-layout
|
||||||
|
/scale-pages
|
||||||
|
/crop
|
||||||
|
/extract-page
|
||||||
|
/pdf-to-single-page
|
||||||
|
/img-to-pdf
|
||||||
|
/markdown-to-pdf
|
||||||
|
/pdf-to-img
|
||||||
|
/pdf-to-text
|
||||||
|
/pdf-to-csv
|
||||||
|
/sign
|
||||||
|
/add-password
|
||||||
|
/remove-password
|
||||||
|
/change-permissions
|
||||||
|
/add-watermark
|
||||||
|
/cert-sign
|
||||||
|
/validate-signature
|
||||||
|
/remove-cert-sign
|
||||||
|
/sanitize-pdf
|
||||||
|
/auto-redact
|
||||||
|
/redact
|
||||||
|
/stamp
|
||||||
|
/view-pdf
|
||||||
|
/add-page-numbers
|
||||||
|
/add-image
|
||||||
|
/extract-images
|
||||||
|
/flatten
|
||||||
|
/remove-annotations
|
||||||
|
/remove-blanks
|
||||||
|
/compare
|
||||||
|
/change-metadata
|
||||||
|
/get-info-on-pdf
|
||||||
|
/remove-image-pdf
|
||||||
|
/replace-and-invert-color-pdf
|
||||||
|
/pipeline
|
||||||
|
/auto-rename
|
||||||
|
/adjust-contrast
|
||||||
|
/overlay-pdf
|
||||||
|
/auto-split-pdf
|
||||||
|
/split-pdf-by-sections
|
||||||
|
/split-pdf-by-chapters
|
||||||
|
/split-by-size-or-count
|
||||||
|
/show-javascript
|
||||||
|
/swagger-ui/index.html
|
||||||
|
/licenses
|
||||||
|
/releases
|
||||||
@@ -60,4 +60,4 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
volumes:
|
volumes:
|
||||||
- ./stirling/latest/data:/pgdata
|
- ./stirling/latest/data:/pgdata
|
||||||
|
|||||||
7
gradle.properties
Normal file
7
gradle.properties
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Enables parallel execution of tasks, allowing multiple tasks to run simultaneously
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
|
# Enables build caching to reuse outputs from previous builds for faster execution
|
||||||
|
# org.gradle.caching=true
|
||||||
|
|
||||||
|
org.gradle.build-scan=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
37
gradlew
vendored
37
gradlew
vendored
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +82,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +133,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|||||||
23
gradlew.bat
vendored
23
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -135,9 +135,10 @@ def compare_files(
|
|||||||
# elif "language.direction" in sort_ignore_translation[language]["missing"]:
|
# elif "language.direction" in sort_ignore_translation[language]["missing"]:
|
||||||
# sort_ignore_translation[language]["missing"].remove("language.direction")
|
# sort_ignore_translation[language]["missing"].remove("language.direction")
|
||||||
|
|
||||||
with open(default_file_path, encoding="utf-8") as default_file, open(
|
with (
|
||||||
file_path, encoding="utf-8"
|
open(default_file_path, encoding="utf-8") as default_file,
|
||||||
) as file:
|
open(file_path, encoding="utf-8") as file,
|
||||||
|
):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
next(default_file)
|
next(default_file)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public class EEAppConfig {
|
|||||||
|
|
||||||
@Bean(name = "runningEE")
|
@Bean(name = "runningEE")
|
||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "SSOAutoLogin")
|
||||||
|
public boolean ssoAutoLogin() {
|
||||||
|
return applicationProperties.getEnterpriseEdition().isSsoAutoLogin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class KeygenLicenseVerifier {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.info(" validateLicenseResponse body: " + response.body());
|
log.debug(" validateLicenseResponse body: " + response.body());
|
||||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Python", "extract-image-scans");
|
addEndpointToGroup("Python", "extract-image-scans");
|
||||||
addEndpointToGroup("Python", "html-to-pdf");
|
addEndpointToGroup("Python", "html-to-pdf");
|
||||||
addEndpointToGroup("Python", "url-to-pdf");
|
addEndpointToGroup("Python", "url-to-pdf");
|
||||||
addEndpointToGroup("Python", "pdf-to-img");
|
|
||||||
addEndpointToGroup("Python", "file-to-pdf");
|
addEndpointToGroup("Python", "file-to-pdf");
|
||||||
|
|
||||||
// openCV
|
// openCV
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class InstallationPathConfig {
|
|||||||
// Pipeline paths
|
// Pipeline paths
|
||||||
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
|
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
|
||||||
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
|
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
|
private static final String PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
|
||||||
// Custom file paths
|
// Custom file paths
|
||||||
private static final String STATIC_PATH;
|
private static final String STATIC_PATH;
|
||||||
@@ -45,6 +46,7 @@ public class InstallationPathConfig {
|
|||||||
// Initialize pipeline paths
|
// Initialize pipeline paths
|
||||||
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator;
|
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator;
|
||||||
PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator;
|
PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator;
|
||||||
|
PIPELINE_DEFAULT_WEB_UI_CONFIGS = PIPELINE_PATH + "defaultWebUIConfigs" + File.separator;
|
||||||
|
|
||||||
// Initialize custom file paths
|
// Initialize custom file paths
|
||||||
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||||
@@ -118,6 +120,10 @@ public class InstallationPathConfig {
|
|||||||
return PIPELINE_FINISHED_FOLDERS_PATH;
|
return PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPipelineDefaultWebUIConfigsDir() {
|
||||||
|
return PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticPath() {
|
public static String getStaticPath() {
|
||||||
return STATIC_PATH;
|
return STATIC_PATH;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,24 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
@@ -43,24 +28,16 @@ import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
|||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
||||||
|
|
||||||
@@ -72,7 +49,7 @@ import stirling.software.SPDF.repository.PersistentLoginRepository;
|
|||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
@Lazy private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
@@ -86,16 +63,10 @@ public class SecurityConfiguration {
|
|||||||
private final FirstLoginFilter firstLoginFilter;
|
private final FirstLoginFilter firstLoginFilter;
|
||||||
private final SessionPersistentRegistry sessionRegistry;
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
private final PersistentLoginRepository persistentLoginRepository;
|
private final PersistentLoginRepository persistentLoginRepository;
|
||||||
|
private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
|
||||||
|
private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
|
||||||
|
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
|
||||||
|
|
||||||
// // Only Dev test
|
|
||||||
// @Bean
|
|
||||||
// public WebSecurityCustomizer webSecurityCustomizer() {
|
|
||||||
// return (web) ->
|
|
||||||
// web.ignoring()
|
|
||||||
// .requestMatchers(
|
|
||||||
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
|
||||||
// "/pdfjs-legacy/**");
|
|
||||||
// }
|
|
||||||
public SecurityConfiguration(
|
public SecurityConfiguration(
|
||||||
PersistentLoginRepository persistentLoginRepository,
|
PersistentLoginRepository persistentLoginRepository,
|
||||||
CustomUserDetailsService userDetailsService,
|
CustomUserDetailsService userDetailsService,
|
||||||
@@ -106,7 +77,12 @@ public class SecurityConfiguration {
|
|||||||
UserAuthenticationFilter userAuthenticationFilter,
|
UserAuthenticationFilter userAuthenticationFilter,
|
||||||
LoginAttemptService loginAttemptService,
|
LoginAttemptService loginAttemptService,
|
||||||
FirstLoginFilter firstLoginFilter,
|
FirstLoginFilter firstLoginFilter,
|
||||||
SessionPersistentRegistry sessionRegistry) {
|
SessionPersistentRegistry sessionRegistry,
|
||||||
|
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
|
||||||
|
@Autowired(required = false)
|
||||||
|
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
|
||||||
|
@Autowired(required = false)
|
||||||
|
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
|
||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.loginEnabledValue = loginEnabledValue;
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
@@ -117,6 +93,9 @@ public class SecurityConfiguration {
|
|||||||
this.firstLoginFilter = firstLoginFilter;
|
this.firstLoginFilter = firstLoginFilter;
|
||||||
this.sessionRegistry = sessionRegistry;
|
this.sessionRegistry = sessionRegistry;
|
||||||
this.persistentLoginRepository = persistentLoginRepository;
|
this.persistentLoginRepository = persistentLoginRepository;
|
||||||
|
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
|
||||||
|
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
|
||||||
|
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -274,7 +253,7 @@ public class SecurityConfiguration {
|
|||||||
userService,
|
userService,
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.userAuthoritiesMapper(
|
.userAuthoritiesMapper(
|
||||||
userAuthoritiesMapper()))
|
oAuth2userAuthoritiesMapper))
|
||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
@@ -291,7 +270,7 @@ public class SecurityConfiguration {
|
|||||||
try {
|
try {
|
||||||
saml2.loginPage("/saml2")
|
saml2.loginPage("/saml2")
|
||||||
.relyingPartyRegistrationRepository(
|
.relyingPartyRegistrationRepository(
|
||||||
relyingPartyRegistrations())
|
saml2RelyingPartyRegistrations)
|
||||||
.authenticationManager(
|
.authenticationManager(
|
||||||
new ProviderManager(authenticationProvider))
|
new ProviderManager(authenticationProvider))
|
||||||
.successHandler(
|
.successHandler(
|
||||||
@@ -302,8 +281,7 @@ public class SecurityConfiguration {
|
|||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSaml2AuthenticationFailureHandler())
|
new CustomSaml2AuthenticationFailureHandler())
|
||||||
.authenticationRequestResolver(
|
.authenticationRequestResolver(
|
||||||
authenticationRequestResolver(
|
saml2AuthenticationRequestResolver);
|
||||||
relyingPartyRegistrations()));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error configuring SAML2 login", e);
|
log.error("Error configuring SAML2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -311,244 +289,11 @@ public class SecurityConfiguration {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
|
||||||
// CookieCsrfTokenRepository cookieRepo =
|
|
||||||
// CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
||||||
// CsrfTokenRequestAttributeHandler requestHandler =
|
|
||||||
// new CsrfTokenRequestAttributeHandler();
|
|
||||||
// requestHandler.setCsrfRequestAttributeName(null);
|
|
||||||
// http.csrf(
|
|
||||||
// csrf ->
|
|
||||||
// csrf.csrfTokenRepository(cookieRepo)
|
|
||||||
// .csrfTokenRequestHandler(requestHandler));
|
|
||||||
// }
|
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
|
||||||
Saml2X509Credential signingCredential =
|
|
||||||
new Saml2X509Credential(
|
|
||||||
CertificateUtils.readPrivateKey(privateKeyResource),
|
|
||||||
CertificateUtils.readCertificate(certificateResource),
|
|
||||||
Saml2X509CredentialType.SIGNING);
|
|
||||||
RelyingPartyRegistration rp =
|
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
|
||||||
.signingX509Credentials(c -> c.add(signingCredential))
|
|
||||||
.assertingPartyMetadata(
|
|
||||||
metadata ->
|
|
||||||
metadata.entityId(samlConf.getIdpIssuer())
|
|
||||||
.singleSignOnServiceLocation(
|
|
||||||
samlConf.getIdpSingleLoginUrl())
|
|
||||||
.verificationX509Credentials(
|
|
||||||
c -> c.add(verificationCredential))
|
|
||||||
.singleSignOnServiceBinding(
|
|
||||||
Saml2MessageBinding.POST)
|
|
||||||
.wantAuthnRequestsSigned(true))
|
|
||||||
.build();
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
|
||||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
|
||||||
OpenSaml4AuthenticationRequestResolver resolver =
|
|
||||||
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
|
||||||
resolver.setAuthnRequestCustomizer(
|
|
||||||
customizer -> {
|
|
||||||
log.debug("Customizing SAML Authentication request");
|
|
||||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
|
||||||
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
|
||||||
if (authnRequest.getID() == null) {
|
|
||||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
|
||||||
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
|
||||||
log.debug(
|
|
||||||
"AuthnRequest Issuer: {}",
|
|
||||||
authnRequest.getIssuer() != null
|
|
||||||
? authnRequest.getIssuer().getValue()
|
|
||||||
: "null");
|
|
||||||
HttpServletRequest request = customizer.getRequest();
|
|
||||||
// Log HTTP request details
|
|
||||||
log.debug("HTTP Request Method: {}", request.getMethod());
|
|
||||||
log.debug("Request URI: {}", request.getRequestURI());
|
|
||||||
log.debug("Request URL: {}", request.getRequestURL().toString());
|
|
||||||
log.debug("Query String: {}", request.getQueryString());
|
|
||||||
log.debug("Remote Address: {}", request.getRemoteAddr());
|
|
||||||
// Log headers
|
|
||||||
Collections.list(request.getHeaderNames())
|
|
||||||
.forEach(
|
|
||||||
headerName -> {
|
|
||||||
log.debug(
|
|
||||||
"Header - {}: {}",
|
|
||||||
headerName,
|
|
||||||
request.getHeader(headerName));
|
|
||||||
});
|
|
||||||
// Log SAML specific parameters
|
|
||||||
log.debug("SAML Request Parameters:");
|
|
||||||
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
|
||||||
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
|
||||||
// Log session debugrmation if exists
|
|
||||||
if (request.getSession(false) != null) {
|
|
||||||
log.debug("Session ID: {}", request.getSession().getId());
|
|
||||||
}
|
|
||||||
// Log any assertions consumer service details if present
|
|
||||||
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
|
||||||
log.debug(
|
|
||||||
"AssertionConsumerServiceURL: {}",
|
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
|
||||||
}
|
|
||||||
// Log NameID policy if present
|
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
|
||||||
log.debug(
|
|
||||||
"NameIDPolicy Format: {}",
|
|
||||||
authnRequest.getNameIDPolicy().getFormat());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
provider.setUserDetailsService(userDetailsService);
|
provider.setUserDetailsService(userDetailsService);
|
||||||
@@ -556,46 +301,6 @@ public class SecurityConfiguration {
|
|||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
|
||||||
return (authorities) -> {
|
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
authorities.forEach(
|
|
||||||
authority -> {
|
|
||||||
// Add existing OAUTH2 Authorities
|
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
|
||||||
String useAsUsername =
|
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
|
||||||
.getOauth2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
// Example limit TODO add config level
|
// Example limit TODO add config level
|
||||||
|
|||||||
@@ -329,12 +329,16 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public boolean isUsernameValid(String username) {
|
public boolean isUsernameValid(String username) {
|
||||||
// Checks whether the simple username is formatted correctly
|
// Checks whether the simple username is formatted correctly
|
||||||
|
// Regular expression for user name: Min. 3 characters, max. 50 characters
|
||||||
boolean isValidSimpleUsername =
|
boolean isValidSimpleUsername =
|
||||||
username.matches("^[a-zA-Z0-9][a-zA-Z0-9@._+-]*[a-zA-Z0-9]$");
|
username.matches("^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$");
|
||||||
|
|
||||||
// Checks whether the email address is formatted correctly
|
// Checks whether the email address is formatted correctly
|
||||||
|
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
|
||||||
boolean isValidEmail =
|
boolean isValidEmail =
|
||||||
username.matches(
|
username.matches(
|
||||||
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
"^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\\\\.[A-Za-z0-9-]+)*(?:\\\\.[A-Za-z]{2,})$");
|
||||||
|
|
||||||
List<String> notAllowedUserList = new ArrayList<>();
|
List<String> notAllowedUserList = new ArrayList<>();
|
||||||
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
||||||
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security.database;
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -9,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@@ -17,8 +20,8 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class DatabaseConfig {
|
public class DatabaseConfig {
|
||||||
|
|
||||||
public static final String DATASOURCE_DEFAULT_URL =
|
public final String DATASOURCE_DEFAULT_URL;
|
||||||
"jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
|
||||||
public static final String DATASOURCE_URL_TEMPLATE = "jdbc:%s://%s:%4d/%s";
|
public static final String DATASOURCE_URL_TEMPLATE = "jdbc:%s://%s:%4d/%s";
|
||||||
public static final String DEFAULT_DRIVER = "org.h2.Driver";
|
public static final String DEFAULT_DRIVER = "org.h2.Driver";
|
||||||
public static final String DEFAULT_USERNAME = "sa";
|
public static final String DEFAULT_USERNAME = "sa";
|
||||||
@@ -27,7 +30,10 @@ public class DatabaseConfig {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final boolean runningEE;
|
private final boolean runningEE;
|
||||||
|
|
||||||
public DatabaseConfig(ApplicationProperties applicationProperties, @Qualifier("runningEE") boolean runningEE) {
|
public DatabaseConfig(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
@Qualifier("runningEE") boolean runningEE) {
|
||||||
|
DATASOURCE_DEFAULT_URL = "jdbc:h2:file:" + InstallationPathConfig.getConfigPath() + File.separator + "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.runningEE = runningEE;
|
this.runningEE = runningEE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.springframework.jdbc.datasource.init.ScriptException;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.exception.BackupNotFoundException;
|
import stirling.software.SPDF.model.exception.BackupNotFoundException;
|
||||||
@@ -37,12 +38,14 @@ public class DatabaseService implements DatabaseInterface {
|
|||||||
|
|
||||||
public static final String BACKUP_PREFIX = "backup_";
|
public static final String BACKUP_PREFIX = "backup_";
|
||||||
public static final String SQL_SUFFIX = ".sql";
|
public static final String SQL_SUFFIX = ".sql";
|
||||||
private static final String BACKUP_DIR = "configs/db/backup/";
|
private final Path BACKUP_DIR;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
|
|
||||||
public DatabaseService(ApplicationProperties applicationProperties, DataSource dataSource) {
|
public DatabaseService(ApplicationProperties applicationProperties, DataSource dataSource) {
|
||||||
|
this.BACKUP_DIR =
|
||||||
|
Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
@@ -55,9 +58,9 @@ public class DatabaseService implements DatabaseInterface {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean hasBackup() {
|
public boolean hasBackup() {
|
||||||
Path filePath = Paths.get(BACKUP_DIR);
|
createBackupDirectory();
|
||||||
|
|
||||||
if (Files.exists(filePath)) {
|
if (Files.exists(BACKUP_DIR)) {
|
||||||
return !getBackupList().isEmpty();
|
return !getBackupList().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +77,11 @@ public class DatabaseService implements DatabaseInterface {
|
|||||||
List<FileInfo> backupFiles = new ArrayList<>();
|
List<FileInfo> backupFiles = new ArrayList<>();
|
||||||
|
|
||||||
if (isH2Database()) {
|
if (isH2Database()) {
|
||||||
Path backupPath = Paths.get(BACKUP_DIR);
|
createBackupDirectory();
|
||||||
|
|
||||||
try (DirectoryStream<Path> stream =
|
try (DirectoryStream<Path> stream =
|
||||||
Files.newDirectoryStream(
|
Files.newDirectoryStream(
|
||||||
backupPath,
|
BACKUP_DIR,
|
||||||
path ->
|
path ->
|
||||||
path.getFileName().toString().startsWith(BACKUP_PREFIX)
|
path.getFileName().toString().startsWith(BACKUP_PREFIX)
|
||||||
&& path.getFileName()
|
&& path.getFileName()
|
||||||
@@ -110,6 +113,17 @@ public class DatabaseService implements DatabaseInterface {
|
|||||||
return backupFiles;
|
return backupFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createBackupDirectory() {
|
||||||
|
if (!Files.exists(BACKUP_DIR)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(BACKUP_DIR);
|
||||||
|
log.debug("create backup directory: {}", BACKUP_DIR);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error create backup directory: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importDatabase() {
|
public void importDatabase() {
|
||||||
if (!hasBackup()) throw new BackupNotFoundException("No backup scripts were found.");
|
if (!hasBackup()) throw new BackupNotFoundException("No backup scripts were found.");
|
||||||
@@ -255,7 +269,8 @@ public class DatabaseService implements DatabaseInterface {
|
|||||||
* @return the <code>Path</code> object for the given file name
|
* @return the <code>Path</code> object for the given file name
|
||||||
*/
|
*/
|
||||||
public Path getBackupFilePath(String fileName) {
|
public Path getBackupFilePath(String fileName) {
|
||||||
Path filePath = Paths.get(BACKUP_DIR, fileName).normalize();
|
createBackupDirectory();
|
||||||
|
Path filePath = BACKUP_DIR.resolve(fileName).normalize();
|
||||||
if (!filePath.startsWith(BACKUP_DIR)) {
|
if (!filePath.startsWith(BACKUP_DIR)) {
|
||||||
throw new SecurityException("Path traversal detected");
|
throw new SecurityException("Path traversal detected");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
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.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class OAuth2Configuration {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
@Lazy private final UserService userService;
|
||||||
|
|
||||||
|
public OAuth2Configuration(
|
||||||
|
ApplicationProperties applicationProperties, @Lazy UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
if (registrations.isEmpty()) {
|
||||||
|
log.error("At least one OAuth2 provider must be configured");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
return google != null && google.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(google.getName())
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationuri())
|
||||||
|
.tokenUri(google.getTokenuri())
|
||||||
|
.userInfoUri(google.getUserinfouri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername())
|
||||||
|
.clientName(google.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
|
.registrationId(keycloak.getName())
|
||||||
|
.clientId(keycloak.getClientId())
|
||||||
|
.clientSecret(keycloak.getClientSecret())
|
||||||
|
.scope(keycloak.getScopes())
|
||||||
|
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||||
|
.clientName(keycloak.getClientName())
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
|
return github != null && github.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(github.getName())
|
||||||
|
.clientId(github.getClientId())
|
||||||
|
.clientSecret(github.getClientSecret())
|
||||||
|
.scope(github.getScopes())
|
||||||
|
.authorizationUri(github.getAuthorizationuri())
|
||||||
|
.tokenUri(github.getTokenuri())
|
||||||
|
.userInfoUri(github.getUserinfouri())
|
||||||
|
.userNameAttributeName(github.getUseAsUsername())
|
||||||
|
.clientName(github.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null
|
||||||
|
|| oauth.getIssuer() == null
|
||||||
|
|| oauth.getIssuer().isEmpty()
|
||||||
|
|| oauth.getClientId() == null
|
||||||
|
|| oauth.getClientId().isEmpty()
|
||||||
|
|| oauth.getClientSecret() == null
|
||||||
|
|| oauth.getClientSecret().isEmpty()
|
||||||
|
|| oauth.getScopes() == null
|
||||||
|
|| oauth.getScopes().isEmpty()
|
||||||
|
|| oauth.getUseAsUsername() == null
|
||||||
|
|| oauth.getUseAsUsername().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
|
.registrationId("oidc")
|
||||||
|
.clientId(oauth.getClientId())
|
||||||
|
.clientSecret(oauth.getClientSecret())
|
||||||
|
.scope(oauth.getScopes())
|
||||||
|
.userNameAttributeName(oauth.getUseAsUsername())
|
||||||
|
.clientName("OIDC")
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||||
|
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
|
return (authorities) -> {
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
authorities.forEach(
|
||||||
|
authority -> {
|
||||||
|
// Add existing OAUTH2 Authorities
|
||||||
|
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||||
|
// Add Authorities from database for existing user, if user is present.
|
||||||
|
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||||
|
String useAsUsername =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getOauth2()
|
||||||
|
.getUseAsUsername();
|
||||||
|
Optional<User> userOpt =
|
||||||
|
userService.findByUsernameIgnoreCase(
|
||||||
|
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
User user = userOpt.get();
|
||||||
|
if (user != null) {
|
||||||
|
mappedAuthorities.add(
|
||||||
|
new SimpleGrantedAuthority(
|
||||||
|
userService.findRole(user).getAuthority()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mappedAuthorities;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml2;
|
||||||
|
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||||
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class SAML2Configuration {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
||||||
|
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
||||||
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
|
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
||||||
|
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
||||||
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
|
Resource certificateResource = samlConf.getSpCert();
|
||||||
|
Saml2X509Credential signingCredential =
|
||||||
|
new Saml2X509Credential(
|
||||||
|
CertificateUtils.readPrivateKey(privateKeyResource),
|
||||||
|
CertificateUtils.readCertificate(certificateResource),
|
||||||
|
Saml2X509CredentialType.SIGNING);
|
||||||
|
RelyingPartyRegistration rp =
|
||||||
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
|
.signingX509Credentials(c -> c.add(signingCredential))
|
||||||
|
.assertingPartyMetadata(
|
||||||
|
metadata ->
|
||||||
|
metadata.entityId(samlConf.getIdpIssuer())
|
||||||
|
.singleSignOnServiceLocation(
|
||||||
|
samlConf.getIdpSingleLoginUrl())
|
||||||
|
.verificationX509Credentials(
|
||||||
|
c -> c.add(verificationCredential))
|
||||||
|
.singleSignOnServiceBinding(
|
||||||
|
Saml2MessageBinding.POST)
|
||||||
|
.wantAuthnRequestsSigned(true))
|
||||||
|
.build();
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||||
|
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||||
|
OpenSaml4AuthenticationRequestResolver resolver =
|
||||||
|
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||||
|
resolver.setAuthnRequestCustomizer(
|
||||||
|
customizer -> {
|
||||||
|
log.debug("Customizing SAML Authentication request");
|
||||||
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
|
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
||||||
|
if (authnRequest.getID() == null) {
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
||||||
|
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
||||||
|
log.debug(
|
||||||
|
"AuthnRequest Issuer: {}",
|
||||||
|
authnRequest.getIssuer() != null
|
||||||
|
? authnRequest.getIssuer().getValue()
|
||||||
|
: "null");
|
||||||
|
HttpServletRequest request = customizer.getRequest();
|
||||||
|
// Log HTTP request details
|
||||||
|
log.debug("HTTP Request Method: {}", request.getMethod());
|
||||||
|
log.debug("Request URI: {}", request.getRequestURI());
|
||||||
|
log.debug("Request URL: {}", request.getRequestURL().toString());
|
||||||
|
log.debug("Query String: {}", request.getQueryString());
|
||||||
|
log.debug("Remote Address: {}", request.getRemoteAddr());
|
||||||
|
// Log headers
|
||||||
|
Collections.list(request.getHeaderNames())
|
||||||
|
.forEach(
|
||||||
|
headerName -> {
|
||||||
|
log.debug(
|
||||||
|
"Header - {}: {}",
|
||||||
|
headerName,
|
||||||
|
request.getHeader(headerName));
|
||||||
|
});
|
||||||
|
// Log SAML specific parameters
|
||||||
|
log.debug("SAML Request Parameters:");
|
||||||
|
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
||||||
|
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
||||||
|
// Log session debugrmation if exists
|
||||||
|
if (request.getSession(false) != null) {
|
||||||
|
log.debug("Session ID: {}", request.getSession().getId());
|
||||||
|
}
|
||||||
|
// Log any assertions consumer service details if present
|
||||||
|
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
||||||
|
log.debug(
|
||||||
|
"AssertionConsumerServiceURL: {}",
|
||||||
|
authnRequest.getAssertionConsumerServiceURL());
|
||||||
|
}
|
||||||
|
// Log NameID policy if present
|
||||||
|
if (authnRequest.getNameIDPolicy() != null) {
|
||||||
|
log.debug(
|
||||||
|
"NameIDPolicy Format: {}",
|
||||||
|
authnRequest.getNameIDPolicy().getFormat());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
|
||||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||||
@@ -53,12 +52,17 @@ public class RedactController {
|
|||||||
|
|
||||||
@InitBinder
|
@InitBinder
|
||||||
public void initBinder(WebDataBinder binder) {
|
public void initBinder(WebDataBinder binder) {
|
||||||
binder.registerCustomEditor(List.class, "redactions", new StringToArrayListPropertyEditor());
|
binder.registerCustomEditor(
|
||||||
|
List.class, "redactions", new StringToArrayListPropertyEditor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/redact", consumes = "multipart/form-data")
|
@PostMapping(value = "/redact", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Redacts areas and pages in a PDF document", description = "This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
|
@Operation(
|
||||||
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request) throws IOException {
|
summary = "Redacts areas and pages in a PDF document",
|
||||||
|
description =
|
||||||
|
"This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
|
||||||
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
List<RedactionArea> redactionAreas = request.getRedactions();
|
List<RedactionArea> redactionAreas = request.getRedactions();
|
||||||
|
|
||||||
@@ -86,18 +90,22 @@ public class RedactController {
|
|||||||
+ "_redacted.pdf");
|
+ "_redacted.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redactAreas(List<RedactionArea> redactionAreas, PDDocument document, PDPageTree allPages)
|
private void redactAreas(
|
||||||
|
List<RedactionArea> redactionAreas, PDDocument document, PDPageTree allPages)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Color redactColor = null;
|
Color redactColor = null;
|
||||||
for (RedactionArea redactionArea : redactionAreas) {
|
for (RedactionArea redactionArea : redactionAreas) {
|
||||||
if (redactionArea.getPage() == null || redactionArea.getPage() <= 0
|
if (redactionArea.getPage() == null
|
||||||
|| redactionArea.getHeight() == null || redactionArea.getHeight() <= 0.0D
|
|| redactionArea.getPage() <= 0
|
||||||
|| redactionArea.getWidth() == null || redactionArea.getWidth() <= 0.0D)
|
|| redactionArea.getHeight() == null
|
||||||
continue;
|
|| redactionArea.getHeight() <= 0.0D
|
||||||
|
|| redactionArea.getWidth() == null
|
||||||
|
|| redactionArea.getWidth() <= 0.0D) continue;
|
||||||
PDPage page = allPages.get(redactionArea.getPage() - 1);
|
PDPage page = allPages.get(redactionArea.getPage() - 1);
|
||||||
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);
|
redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);
|
||||||
contentStream.setNonStrokingColor(redactColor);
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
@@ -114,15 +122,17 @@ public class RedactController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redactPages(ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
|
private void redactPages(
|
||||||
|
ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);
|
Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);
|
||||||
List<Integer> pageNumbers = getPageNumbers(request, allPages.getCount());
|
List<Integer> pageNumbers = getPageNumbers(request, allPages.getCount());
|
||||||
for (Integer pageNumber : pageNumbers) {
|
for (Integer pageNumber : pageNumbers) {
|
||||||
PDPage page = allPages.get(pageNumber);
|
PDPage page = allPages.get(pageNumber);
|
||||||
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
contentStream.setNonStrokingColor(redactColor);
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
PDRectangle box = page.getBBox();
|
PDRectangle box = page.getBBox();
|
||||||
@@ -146,8 +156,10 @@ public class RedactController {
|
|||||||
|
|
||||||
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
|
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
|
||||||
String pageNumbersInput = request.getPageNumbers();
|
String pageNumbersInput = request.getPageNumbers();
|
||||||
String[] parsedPageNumbers = pageNumbersInput != null ? pageNumbersInput.split(",") : new String[0];
|
String[] parsedPageNumbers =
|
||||||
List<Integer> pageNumbers = GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);
|
pageNumbersInput != null ? pageNumbersInput.split(",") : new String[0];
|
||||||
|
List<Integer> pageNumbers =
|
||||||
|
GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);
|
||||||
Collections.sort(pageNumbers);
|
Collections.sort(pageNumbers);
|
||||||
return pageNumbers;
|
return pageNumbers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ public class WatermarkController {
|
|||||||
+ Math.abs(watermarkHeight * Math.cos(radians)));
|
+ Math.abs(watermarkHeight * Math.cos(radians)));
|
||||||
|
|
||||||
// Calculating the number of rows and columns.
|
// Calculating the number of rows and columns.
|
||||||
|
|
||||||
int watermarkRows = (int) (pageHeight / newWatermarkHeight + 1);
|
int watermarkRows = (int) (pageHeight / newWatermarkHeight + 1);
|
||||||
int watermarkCols = (int) (pageWidth / newWatermarkWidth + 1);
|
int watermarkCols = (int) (pageWidth / newWatermarkWidth + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ public class GeneralWebController {
|
|||||||
model.addAttribute("currentPage", "pipeline");
|
model.addAttribute("currentPage", "pipeline");
|
||||||
List<String> pipelineConfigs = new ArrayList<>();
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
if (new File("./pipeline/defaultWebUIConfigs/").exists()) {
|
if (new File(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()).exists()) {
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
try (Stream<Path> paths = Files.walk(Paths.get(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()))) {
|
||||||
List<Path> jsonFiles =
|
List<Path> jsonFiles =
|
||||||
paths.filter(Files::isRegularFile)
|
paths.filter(Files::isRegularFile)
|
||||||
.filter(p -> p.toString().endsWith(".json"))
|
.filter(p -> p.toString().endsWith(".json"))
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ public class ApplicationProperties {
|
|||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
@ToString.Exclude private String key;
|
@ToString.Exclude private String key;
|
||||||
private int maxUsers;
|
private int maxUsers;
|
||||||
|
private boolean ssoAutoLogin;
|
||||||
private CustomMetadata customMetadata = new CustomMetadata();
|
private CustomMetadata customMetadata = new CustomMetadata();
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api.security;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package stirling.software.SPDF.model.api.security;
|
package stirling.software.SPDF.model.api.security;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class RedactionArea {
|
public class RedactionArea {
|
||||||
@Schema(description = "The left edge point of the area to be redacted.")
|
@Schema(description = "The left edge point of the area to be redacted.")
|
||||||
private Double x;
|
private Double x;
|
||||||
|
|
||||||
@Schema(description = "The top edge point of the area to be redacted.")
|
@Schema(description = "The top edge point of the area to be redacted.")
|
||||||
private Double y;
|
private Double y;
|
||||||
|
|
||||||
@Schema(description = "The height of the area to be redacted.")
|
@Schema(description = "The height of the area to be redacted.")
|
||||||
private Double height;
|
private Double height;
|
||||||
|
|
||||||
@Schema(description = "The width of the area to be redacted.")
|
@Schema(description = "The width of the area to be redacted.")
|
||||||
private Double width;
|
private Double width;
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class StringToArrayListPropertyEditor extends PropertyEditorSupport {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||||
TypeReference<ArrayList<RedactionArea>> typeRef = new TypeReference<ArrayList<RedactionArea>>() {
|
TypeReference<ArrayList<RedactionArea>> typeRef =
|
||||||
};
|
new TypeReference<ArrayList<RedactionArea>>() {};
|
||||||
List<RedactionArea> list = objectMapper.readValue(text, typeRef);
|
List<RedactionArea> list = objectMapper.readValue(text, typeRef);
|
||||||
setValue(list);
|
setValue(list);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=تحويل إلى PDF
|
|||||||
compress.title=ضغط
|
compress.title=ضغط
|
||||||
compress.header=ضغط ملف PDF
|
compress.header=ضغط ملف PDF
|
||||||
compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
|
compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
|
||||||
compress.selectText.1=الوضع اليدوي - من 1 إلى 4
|
compress.selectText.1=الوضع اليدوي - من 1 إلى 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=مستوى التحسين:
|
compress.selectText.2=مستوى التحسين:
|
||||||
compress.selectText.3=4 (رهيب للصور النصية)
|
compress.selectText.3=4 (رهيب للصور النصية)
|
||||||
compress.selectText.4=الوضع التلقائي - يضبط الجودة تلقائيًا للحصول على ملف PDF بالحجم المحدد
|
compress.selectText.4=الوضع التلقائي - يضبط الجودة تلقائيًا للحصول على ملف PDF بالحجم المحدد
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDF-ə Çevir
|
|||||||
compress.title=Sıxışdır
|
compress.title=Sıxışdır
|
||||||
compress.header=PDF-i Sıxışdır
|
compress.header=PDF-i Sıxışdır
|
||||||
compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
|
compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
|
||||||
compress.selectText.1=Manual Mod - 1-dən 4-ə
|
compress.selectText.1=Manual Mod - 1-dən 5-ə
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizasiya səviyyəsi:
|
compress.selectText.2=Optimizasiya səviyyəsi:
|
||||||
compress.selectText.3=4 (Mətn şəkilləri üçün yaxşı deyil)
|
compress.selectText.3=4 (Mətn şəkilləri üçün yaxşı deyil)
|
||||||
compress.selectText.4=Avto mod - PDF-in dəqiq ölçüsünü əldə etmək üçün keyfiyyəti avtomatik tənzimləyir
|
compress.selectText.4=Avto mod - PDF-in dəqiq ölçüsünü əldə etmək üçün keyfiyyəti avtomatik tənzimləyir
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Преобразуване към PDF
|
|||||||
compress.title=Компресиране
|
compress.title=Компресиране
|
||||||
compress.header=Компресиране на PDF
|
compress.header=Компресиране на PDF
|
||||||
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
||||||
compress.selectText.1=Ръчен режим - от 1 до 4
|
compress.selectText.1=Ръчен режим - от 1 до 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Ниво на оптимизация:
|
compress.selectText.2=Ниво на оптимизация:
|
||||||
compress.selectText.3=4 (Ужасно за текстови изображения)
|
compress.selectText.3=4 (Ужасно за текстови изображения)
|
||||||
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF с точен размер
|
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF с точен размер
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Converteix a PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
|
compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
|
||||||
compress.selectText.1=Mode manual: de l'1 al 4
|
compress.selectText.1=Mode manual: de l'1 al 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivell d'optimització:
|
compress.selectText.2=Nivell d'optimització:
|
||||||
compress.selectText.3=4 (terrible per a imatges de text)
|
compress.selectText.3=4 (terrible per a imatges de text)
|
||||||
compress.selectText.4=Mode automàtic: ajusta automàticament la qualitat perquè el PDF tingui la mida exacta
|
compress.selectText.4=Mode automàtic: ajusta automàticament la qualitat perquè el PDF tingui la mida exacta
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Převést na PDF
|
|||||||
compress.title=Komprese
|
compress.title=Komprese
|
||||||
compress.header=Komprimovat PDF
|
compress.header=Komprimovat PDF
|
||||||
compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
|
compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
|
||||||
compress.selectText.1=Ruční režim - Od 1 do 4
|
compress.selectText.1=Ruční režim - Od 1 do 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Úroveň optimalizace:
|
compress.selectText.2=Úroveň optimalizace:
|
||||||
compress.selectText.3=4 (Hrozné pro textové obrázky)
|
compress.selectText.3=4 (Hrozné pro textové obrázky)
|
||||||
compress.selectText.4=Automatický režim - Automaticky upravuje kvalitu pro dosažení přesné velikosti PDF
|
compress.selectText.4=Automatický režim - Automaticky upravuje kvalitu pro dosažení přesné velikosti PDF
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konvertér til PDF
|
|||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
|
compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
|
||||||
compress.selectText.1=Manuel Tilstand - Fra 1 til 4
|
compress.selectText.1=Manuel Tilstand - Fra 1 til 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsniveau:
|
compress.selectText.2=Optimeringsniveau:
|
||||||
compress.selectText.3=4 (Forfærdelig for tekstbilleder)
|
compress.selectText.3=4 (Forfærdelig for tekstbilleder)
|
||||||
compress.selectText.4=Auto tilstand - Justerer automatisk kvaliteten for at få PDF'en til en præcis størrelse
|
compress.selectText.4=Auto tilstand - Justerer automatisk kvaliteten for at få PDF'en til en præcis størrelse
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=In PDF konvertieren
|
|||||||
compress.title=Komprimieren
|
compress.title=Komprimieren
|
||||||
compress.header=PDF komprimieren
|
compress.header=PDF komprimieren
|
||||||
compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
|
compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
|
||||||
compress.selectText.1=Manueller Modus – Von 1 bis 4
|
compress.selectText.1=Manueller Modus – Von 1 bis 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimierungsstufe:
|
compress.selectText.2=Optimierungsstufe:
|
||||||
compress.selectText.3=4 (Schrecklich für Textbilder)
|
compress.selectText.3=4 (Schrecklich für Textbilder)
|
||||||
compress.selectText.4=Automatischer Modus – Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen
|
compress.selectText.4=Automatischer Modus – Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Μετατροπή σε PDF
|
|||||||
compress.title=Συμπίεση
|
compress.title=Συμπίεση
|
||||||
compress.header=Συμπίεση PDF
|
compress.header=Συμπίεση PDF
|
||||||
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF Συμπίεση/Βελτιστοποίηση.
|
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF Συμπίεση/Βελτιστοποίηση.
|
||||||
compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 4
|
compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Επίπεδο Βελτιστοποίησης:
|
compress.selectText.2=Επίπεδο Βελτιστοποίησης:
|
||||||
compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου)
|
compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου)
|
||||||
compress.selectText.4=Αυτόματη Λειτουργία - Auto mode - Προσαρμόζει αυτόματα την ποιότητα για λήψη PDF στο ακριβές μέγεθος
|
compress.selectText.4=Αυτόματη Λειτουργία - Auto mode - Προσαρμόζει αυτόματα την ποιότητα για λήψη PDF στο ακριβές μέγεθος
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Convert to PDF
|
|||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mode - From 1 to 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimisation level:
|
compress.selectText.2=Optimisation level:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Terrible for text images)
|
||||||
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Convert to PDF
|
|||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mode - From 1 to 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimization level:
|
compress.selectText.2=Optimization level:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Terrible for text images)
|
||||||
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Convertir a PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
||||||
compress.selectText.1=Modo manual - De 1 a 4
|
compress.selectText.1=Modo manual - De 1 a 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivel de optimización:
|
compress.selectText.2=Nivel de optimización:
|
||||||
compress.selectText.3=4 (Terrible para imágenes de texto)
|
compress.selectText.3=4 (Terrible para imágenes de texto)
|
||||||
compress.selectText.4=Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto
|
compress.selectText.4=Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDF bihurtu
|
|||||||
compress.title=Konprimatu
|
compress.title=Konprimatu
|
||||||
compress.header=PDFa konprimatu
|
compress.header=PDFa konprimatu
|
||||||
compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
|
compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
|
||||||
compress.selectText.1=Eskuz 1etik 4ra
|
compress.selectText.1=Eskuz 1etik 5ra
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizazio maila:
|
compress.selectText.2=Optimizazio maila:
|
||||||
compress.selectText.3=4 (Izugarria testu-irudietarako)
|
compress.selectText.3=4 (Izugarria testu-irudietarako)
|
||||||
compress.selectText.4=Automatikoa: automatikoki egokitzen du kalitatea PDFak tamaina doi-doia izan dezan
|
compress.selectText.4=Automatikoa: automatikoki egokitzen du kalitatea PDFak tamaina doi-doia izan dezan
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=تبدیل به PDF
|
|||||||
compress.title=فشردهسازی
|
compress.title=فشردهسازی
|
||||||
compress.header=فشردهسازی PDF
|
compress.header=فشردهسازی PDF
|
||||||
compress.credit=این سرویس از qpdf برای فشردهسازی / بهینهسازی PDF استفاده میکند.
|
compress.credit=این سرویس از qpdf برای فشردهسازی / بهینهسازی PDF استفاده میکند.
|
||||||
compress.selectText.1=حالت دستی - از ۱ تا ۴
|
compress.selectText.1=حالت دستی - از 1 تا 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=سطح بهینهسازی:
|
compress.selectText.2=سطح بهینهسازی:
|
||||||
compress.selectText.3=۴ (خیلی بد برای تصاویر متنی)
|
compress.selectText.3=۴ (خیلی بد برای تصاویر متنی)
|
||||||
compress.selectText.4=حالت خودکار - کیفیت را به طور خودکار تنظیم میکند تا PDF به اندازه دقیق برسد
|
compress.selectText.4=حالت خودکار - کیفیت را به طور خودکار تنظیم میکند تا PDF به اندازه دقیق برسد
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Convertir
|
|||||||
compress.title=Compresser un PDF
|
compress.title=Compresser un PDF
|
||||||
compress.header=Compresser un PDF (lorsque c'est possible!)
|
compress.header=Compresser un PDF (lorsque c'est possible!)
|
||||||
compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
|
compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
|
||||||
compress.selectText.1=Mode manuel – de 1 à 4
|
compress.selectText.1=Mode manuel – de 1 à 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Niveau d'optimisation
|
compress.selectText.2=Niveau d'optimisation
|
||||||
compress.selectText.3=4 (terrible pour les images textuelles)
|
compress.selectText.3=4 (terrible pour les images textuelles)
|
||||||
compress.selectText.4=Mode automatique – ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte
|
compress.selectText.4=Mode automatique – ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Tiontaigh go PDF
|
|||||||
compress.title=Comhbhrúigh
|
compress.title=Comhbhrúigh
|
||||||
compress.header=Comhbhrúigh PDF
|
compress.header=Comhbhrúigh PDF
|
||||||
compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
|
compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
|
||||||
compress.selectText.1=Mód Láimhe - Ó 1 go 4
|
compress.selectText.1=Mód Láimhe - Ó 1 go 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Leibhéal optamaithe:
|
compress.selectText.2=Leibhéal optamaithe:
|
||||||
compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs)
|
compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs)
|
||||||
compress.selectText.4=Mód uathoibríoch - Coigeartaíonn Auto cáilíocht chun PDF a fháil go dtí an méid cruinn
|
compress.selectText.4=Mód uathoibríoch - Coigeartaíonn Auto cáilíocht chun PDF a fháil go dtí an méid cruinn
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -933,7 +933,8 @@ fileToPDF.submit=Pretvori u PDF
|
|||||||
compress.title=Komprimirajte
|
compress.title=Komprimirajte
|
||||||
compress.header=Komprimirajte PDF
|
compress.header=Komprimirajte PDF
|
||||||
compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
|
compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
|
||||||
compress.selectText.1=Ručni režim - Od 1 do 4
|
compress.selectText.1=Ručni režim - Od 1 do 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivo optimizacije:
|
compress.selectText.2=Nivo optimizacije:
|
||||||
compress.selectText.3=4 (Užasno za tekstualne slike)
|
compress.selectText.3=4 (Užasno za tekstualne slike)
|
||||||
compress.selectText.4=Automatski način - Automatski prilagođava kvalitetu kako bi PDF dobio točnu veličinu
|
compress.selectText.4=Automatski način - Automatski prilagođava kvalitetu kako bi PDF dobio točnu veličinu
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -933,7 +933,8 @@ fileToPDF.submit=Konversi ke PDF
|
|||||||
compress.title=Kompres
|
compress.title=Kompres
|
||||||
compress.header=Kompres PDF
|
compress.header=Kompres PDF
|
||||||
compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
|
compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
|
||||||
compress.selectText.1=Mode Manual - Dari 1 hingga 4
|
compress.selectText.1=Mode Manual - Dari 1 hingga 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Tingkat Optimalisasi:
|
compress.selectText.2=Tingkat Optimalisasi:
|
||||||
compress.selectText.3=4 (Buruk untuk gambar teks)
|
compress.selectText.3=4 (Buruk untuk gambar teks)
|
||||||
compress.selectText.4=Mode Otomatis - Menyesuaikan kualitas secara otomatis untuk mendapatkan PDF dengan ukuran yang tepat
|
compress.selectText.4=Mode Otomatis - Menyesuaikan kualitas secara otomatis untuk mendapatkan PDF dengan ukuran yang tepat
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Converti in PDF
|
|||||||
compress.title=Comprimi
|
compress.title=Comprimi
|
||||||
compress.header=Comprimi PDF
|
compress.header=Comprimi PDF
|
||||||
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
|
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
|
||||||
compress.selectText.1=Modalità manuale - Da 1 a 4
|
compress.selectText.1=Modalità manuale - Da 1 a 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Livello di ottimizzazione:
|
compress.selectText.2=Livello di ottimizzazione:
|
||||||
compress.selectText.3=4 (Terribile per le immagini di testo)
|
compress.selectText.3=4 (Terribile per le immagini di testo)
|
||||||
compress.selectText.4=Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF
|
compress.selectText.4=Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDFを変換
|
|||||||
compress.title=圧縮
|
compress.title=圧縮
|
||||||
compress.header=PDFを圧縮
|
compress.header=PDFを圧縮
|
||||||
compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
|
compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
|
||||||
compress.selectText.1=手動モード - 1 から 4
|
compress.selectText.1=手動モード - 1 から 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=品質レベル:
|
compress.selectText.2=品質レベル:
|
||||||
compress.selectText.3=4 (テキスト画像は最悪)
|
compress.selectText.3=4 (テキスト画像は最悪)
|
||||||
compress.selectText.4=自動モード - PDFを正確なサイズにするために品質を自動調整する。
|
compress.selectText.4=自動モード - PDFを正確なサイズにするために品質を自動調整する。
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDF로 변환
|
|||||||
compress.title=압축
|
compress.title=압축
|
||||||
compress.header=PDF 압축
|
compress.header=PDF 압축
|
||||||
compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 qpdf를 사용합니다.
|
compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 qpdf를 사용합니다.
|
||||||
compress.selectText.1=수동 모드 - 1에서 4
|
compress.selectText.1=수동 모드 - 1에서 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=최적화 수준:
|
compress.selectText.2=최적화 수준:
|
||||||
compress.selectText.3=4 (텍스트 이미지에 적합하지 않음)
|
compress.selectText.3=4 (텍스트 이미지에 적합하지 않음)
|
||||||
compress.selectText.4=자동 - 정확한 크기의 PDF 문서를 얻기 위해 품질 자동 조정
|
compress.selectText.4=자동 - 정확한 크기의 PDF 문서를 얻기 위해 품질 자동 조정
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Omzetten naar PDF
|
|||||||
compress.title=Comprimeren
|
compress.title=Comprimeren
|
||||||
compress.header=PDF comprimeren
|
compress.header=PDF comprimeren
|
||||||
compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
|
compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
|
||||||
compress.selectText.1=Handmatige modus - Van 1 tot 4
|
compress.selectText.1=Handmatige modus - Van 1 tot 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimalisatieniveau:
|
compress.selectText.2=Optimalisatieniveau:
|
||||||
compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen)
|
compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen)
|
||||||
compress.selectText.4=Automatische modus - Past kwaliteit automatisch aan om PDF naar exacte grootte te krijgen
|
compress.selectText.4=Automatische modus - Past kwaliteit automatisch aan om PDF naar exacte grootte te krijgen
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konverter til PDF
|
|||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
|
compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
|
||||||
compress.selectText.1=Manuell modus - Fra 1 til 4
|
compress.selectText.1=Manuell modus - Fra 1 til 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsnivå:
|
compress.selectText.2=Optimeringsnivå:
|
||||||
compress.selectText.3=4 (Dårlig for tekstbilder)
|
compress.selectText.3=4 (Dårlig for tekstbilder)
|
||||||
compress.selectText.4=Automatisk modus - Justerer automatisk kvaliteten for å få PDF til nøyaktig størrelse
|
compress.selectText.4=Automatisk modus - Justerer automatisk kvaliteten for å få PDF til nøyaktig størrelse
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konwertuj na PDF
|
|||||||
compress.title=Kompresuj
|
compress.title=Kompresuj
|
||||||
compress.header=Kompresuj PDF
|
compress.header=Kompresuj PDF
|
||||||
compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF.
|
compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF.
|
||||||
compress.selectText.1=Tryb ręczny - Od 1 do 4
|
compress.selectText.1=Tryb ręczny - Od 1 do 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Poziom optymalizacji:
|
compress.selectText.2=Poziom optymalizacji:
|
||||||
compress.selectText.3=4 (Duże dla obrazów tekstowych)
|
compress.selectText.3=4 (Duże dla obrazów tekstowych)
|
||||||
compress.selectText.4=Tryb automatyczny - Automatycznie dostosowuje jakość, aby uzyskać dokładny rozmiar pliku PDF
|
compress.selectText.4=Tryb automatyczny - Automatycznie dostosowuje jakość, aby uzyskać dokładny rozmiar pliku PDF
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Converter para PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir
|
compress.header=Comprimir
|
||||||
compress.credit=Este serviço usa o Qpdf para compressão/otimização de PDF.
|
compress.credit=Este serviço usa o Qpdf para compressão/otimização de PDF.
|
||||||
compress.selectText.1=Modo Manual - 1 (Menos) a 9 (Mais)
|
compress.selectText.1=Modo Manual - De 1 a 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nível de Otimização:
|
compress.selectText.2=Nível de Otimização:
|
||||||
compress.selectText.3=4 (Pior para imagens de texto)
|
compress.selectText.3=4 (Pior para imagens de texto)
|
||||||
compress.selectText.4=Modo Automático - Ajusta automaticamente a qualidade para atingir o tamanho exato desejado
|
compress.selectText.4=Modo Automático - Ajusta automaticamente a qualidade para atingir o tamanho exato desejado
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -933,11 +933,12 @@ fileToPDF.submit=Convertiți în PDF
|
|||||||
compress.title=Comprimare
|
compress.title=Comprimare
|
||||||
compress.header=Comprimare PDF
|
compress.header=Comprimare PDF
|
||||||
compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor.
|
compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor.
|
||||||
compress.selectText.1=Nivel de optimizare:
|
compress.selectText.1=Modul manual - de la 1 la 5
|
||||||
compress.selectText.2=0 (Fără optimizare)
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.3=1 (Implicit, optimizare fără pierdere)
|
compress.selectText.2=Nivel de optimizare:
|
||||||
compress.selectText.4=2 (Optimizare cu pierdere)
|
compress.selectText.3=4 (Îngrozitor pentru imaginile text)
|
||||||
compress.selectText.5=3 (Optimizare cu pierdere, mai agresivă)
|
compress.selectText.4=Mod automat - ajustează automat calitatea pentru a aduce PDF-ul la dimensiunea exactă
|
||||||
|
compress.selectText.5=Dimensiunea PDF așteptată (de ex. 25MB, 10.8MB, 25KB)
|
||||||
compress.submit=Comprimare
|
compress.submit=Comprimare
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Преобразовать в PDF
|
|||||||
compress.title=Сжать
|
compress.title=Сжать
|
||||||
compress.header=Сжать PDF
|
compress.header=Сжать PDF
|
||||||
compress.credit=Эта служба использует qpdf для сжатия/оптимизации PDF.
|
compress.credit=Эта служба использует qpdf для сжатия/оптимизации PDF.
|
||||||
compress.selectText.1=Ручной режим - от 1 до 4
|
compress.selectText.1=Ручной режим - от 1 до 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Уровень оптимизации:
|
compress.selectText.2=Уровень оптимизации:
|
||||||
compress.selectText.3=4 (Ужасно для текстовых изображений)
|
compress.selectText.3=4 (Ужасно для текстовых изображений)
|
||||||
compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения PDF точного размера
|
compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения PDF точного размера
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konvertovať do PDF
|
|||||||
compress.title=Komprimovať
|
compress.title=Komprimovať
|
||||||
compress.header=Komprimovať PDF
|
compress.header=Komprimovať PDF
|
||||||
compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF.
|
compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF.
|
||||||
compress.selectText.1=Manuálny režim - Od 1 do 4
|
compress.selectText.1=Manuálny režim - Od 1 do 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Úroveň optimalizácie:
|
compress.selectText.2=Úroveň optimalizácie:
|
||||||
compress.selectText.3=4 (Hrozné pre textové obrázky)
|
compress.selectText.3=4 (Hrozné pre textové obrázky)
|
||||||
compress.selectText.4=Automatický režim - Automaticky upravuje kvalitu, aby sa PDF dostalo na presnú veľkosť
|
compress.selectText.4=Automatický režim - Automaticky upravuje kvalitu, aby sa PDF dostalo na presnú veľkosť
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konvertuj u PDF
|
|||||||
compress.title=Kompresija
|
compress.title=Kompresija
|
||||||
compress.header=Kompresuj PDF
|
compress.header=Kompresuj PDF
|
||||||
compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a.
|
compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a.
|
||||||
compress.selectText.1=Ručni režim - Od 1 do 4
|
compress.selectText.1=Ručni režim - Od 1 do 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivo optimizacije:
|
compress.selectText.2=Nivo optimizacije:
|
||||||
compress.selectText.3=4 (Užasno za tekstualne slike)
|
compress.selectText.3=4 (Užasno za tekstualne slike)
|
||||||
compress.selectText.4=Automatski režim - Automatski prilagođava kvalitet kako bi PDF bio tačne veličine
|
compress.selectText.4=Automatski režim - Automatski prilagođava kvalitet kako bi PDF bio tačne veličine
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Konvertera till PDF
|
|||||||
compress.title=Komprimera
|
compress.title=Komprimera
|
||||||
compress.header=Komprimera PDF
|
compress.header=Komprimera PDF
|
||||||
compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering.
|
compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering.
|
||||||
compress.selectText.1=Manuellt läge - Från 1 till 4
|
compress.selectText.1=Manuellt läge - Från 1 till 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsnivå:
|
compress.selectText.2=Optimeringsnivå:
|
||||||
compress.selectText.3=4 (Fruktansvärt för textbilder)
|
compress.selectText.3=4 (Fruktansvärt för textbilder)
|
||||||
compress.selectText.4=Autoläge - Autojusterar kvaliteten för att få PDF till exakt storlek
|
compress.selectText.4=Autoläge - Autojusterar kvaliteten för att få PDF till exakt storlek
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=แปลงเป็น PDF
|
|||||||
compress.title=บีบอัด
|
compress.title=บีบอัด
|
||||||
compress.header=บีบอัด PDF
|
compress.header=บีบอัด PDF
|
||||||
compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF
|
compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF
|
||||||
compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 4
|
compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ:
|
compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ:
|
||||||
compress.selectText.3=4 (ไม่ดีสำหรับภาพข้อความ)
|
compress.selectText.3=4 (ไม่ดีสำหรับภาพข้อความ)
|
||||||
compress.selectText.4=โหมดอัตโนมัติ - ปรับคุณภาพอัตโนมัติเพื่อให้ PDF ตรงกับขนาดที่ต้องการ
|
compress.selectText.4=โหมดอัตโนมัติ - ปรับคุณภาพอัตโนมัติเพื่อให้ PDF ตรงกับขนาดที่ต้องการ
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDF'e Dönüştür
|
|||||||
compress.title=Sıkıştır
|
compress.title=Sıkıştır
|
||||||
compress.header=PDF'i Sıkıştır
|
compress.header=PDF'i Sıkıştır
|
||||||
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
|
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
|
||||||
compress.selectText.1=Manuel Mod - 1'den 4'e
|
compress.selectText.1=Manuel Mod - 1'den 5'e
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizasyon seviyesi:
|
compress.selectText.2=Optimizasyon seviyesi:
|
||||||
compress.selectText.3=4 (Metin resimleri için hiç uygun değil)
|
compress.selectText.3=4 (Metin resimleri için hiç uygun değil)
|
||||||
compress.selectText.4=Otomatik mod - PDF'in tam boyutuna ulaşmak için kaliteyi otomatik ayarlar
|
compress.selectText.4=Otomatik mod - PDF'in tam boyutuna ulaşmak için kaliteyi otomatik ayarlar
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Перетворити у PDF
|
|||||||
compress.title=Стиснути
|
compress.title=Стиснути
|
||||||
compress.header=Стиснути PDF
|
compress.header=Стиснути PDF
|
||||||
compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF.
|
compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF.
|
||||||
compress.selectText.1=Ручний режим - від 1 до 4
|
compress.selectText.1=Ручний режим - від 1 до 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Рівень оптимізації:
|
compress.selectText.2=Рівень оптимізації:
|
||||||
compress.selectText.3=4 (Жахливо для текстових зображень)
|
compress.selectText.3=4 (Жахливо для текстових зображень)
|
||||||
compress.selectText.4=Автоматичний режим - автоматично налаштовує якість для отримання PDF точного розміру
|
compress.selectText.4=Автоматичний режим - автоматично налаштовує якість для отримання PDF точного розміру
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=Chuyển đổi sang PDF
|
|||||||
compress.title=Nén
|
compress.title=Nén
|
||||||
compress.header=Nén PDF
|
compress.header=Nén PDF
|
||||||
compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF.
|
compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF.
|
||||||
compress.selectText.1=Chế độ thủ công - Từ 1 đến 4
|
compress.selectText.1=Chế độ thủ công - Từ 1 đến 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Mức độ tối ưu hóa:
|
compress.selectText.2=Mức độ tối ưu hóa:
|
||||||
compress.selectText.3=4 (Tệ cho hình ảnh văn bản)
|
compress.selectText.3=4 (Tệ cho hình ảnh văn bản)
|
||||||
compress.selectText.4=Chế độ tự động - Tự động điều chỉnh chất lượng để đạt được kích thước PDF chính xác
|
compress.selectText.4=Chế độ tự động - Tự động điều chỉnh chất lượng để đạt được kích thước PDF chính xác
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=PDF ལ་བསྒྱུར་བ།
|
|||||||
compress.title=སྡུད་སྒྲིལ།
|
compress.title=སྡུད་སྒྲིལ།
|
||||||
compress.header=PDF སྡུད་སྒྲིལ།
|
compress.header=PDF སྡུད་སྒྲིལ།
|
||||||
compress.credit=ཞབས་ཞུ་འདིས་ PDF སྡུད་སྒྲིལ་/ཡར་རྒྱས་གཏོང་བའི་ཆེད་དུ་ qpdf བེད་སྤྱོད་བྱེད་པ།
|
compress.credit=ཞབས་ཞུ་འདིས་ PDF སྡུད་སྒྲིལ་/ཡར་རྒྱས་གཏོང་བའི་ཆེད་དུ་ qpdf བེད་སྤྱོད་བྱེད་པ།
|
||||||
compress.selectText.1=ལག་བཟོས་<EFBFBD><EFBFBD>ྣམ་པ། - 1 ནས་ 4 བར།
|
compress.selectText.1=Manual Mode - From 1 to 4
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimisation level:
|
compress.selectText.2=Optimisation level:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Terrible for text images)
|
||||||
compress.selectText.4=རང་འགུལ་རྣམ་པ། - PDF ཏག་ཏག་ཆེ་ཆུང་ཚད་ལ་འཁྲིད་པའི་ཆེད་དུ་སྤུས་ཚད་རང་འགུལ་གྱིས་སྙོམ་སྒྲིག་བྱེད་པ།
|
compress.selectText.4=རང་འགུལ་རྣམ་པ། - PDF ཏག་ཏག་ཆེ་ཆུང་ཚད་ལ་འཁྲིད་པའི་ཆེད་དུ་སྤུས་ཚད་རང་འགུལ་གྱིས་སྙོམ་སྒྲིག་བྱེད་པ།
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ fileToPDF.submit=转换为 PDF
|
|||||||
compress.title=压缩
|
compress.title=压缩
|
||||||
compress.header=压缩 PDF
|
compress.header=压缩 PDF
|
||||||
compress.credit=此服务使用qpdf进行 PDF 压缩/优化。
|
compress.credit=此服务使用qpdf进行 PDF 压缩/优化。
|
||||||
compress.selectText.1=手动模式 - 从 1 到 4
|
compress.selectText.1=手动模式 - 从 1 到 5
|
||||||
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=优化级别:
|
compress.selectText.2=优化级别:
|
||||||
compress.selectText.3=4(文本图像很糟糕)
|
compress.selectText.3=4(文本图像很糟糕)
|
||||||
compress.selectText.4=自动模式 - 自动调整质量以获得精确大小的PDF
|
compress.selectText.4=自动模式 - 自动调整质量以获得精确大小的PDF
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user