Compare commits
124 Commits
moveDocsTo
...
v0.37.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f9d8d6351 | ||
|
|
e6874cb219 | ||
|
|
b4b5a2419b | ||
|
|
c888cef023 | ||
|
|
0e807d72ef | ||
|
|
3799ab529f | ||
|
|
65370a19a2 | ||
|
|
2dbd58860e | ||
|
|
63bfcc2011 | ||
|
|
ee3496255b | ||
|
|
db945bc833 | ||
|
|
fc40aaca63 | ||
|
|
313116ebcb | ||
|
|
1de33cf36c | ||
|
|
fcb3d48a1f | ||
|
|
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 | ||
|
|
1ed1b17510 | ||
|
|
ded3915424 | ||
|
|
a979b526c4 | ||
|
|
5272e4c7ff | ||
|
|
e95cbecab4 | ||
|
|
85d2f6f0cc | ||
|
|
5ef8c6be55 | ||
|
|
f8e1ce6a7b | ||
|
|
ad50e90a03 | ||
|
|
a4afe5b708 | ||
|
|
d1ed70146c | ||
|
|
fe84128297 | ||
|
|
690527aabd | ||
|
|
2b27cbbd4c | ||
|
|
544e838f7c | ||
|
|
f379c27bd7 | ||
|
|
8d4c762c7e | ||
|
|
bad5a2bc8b | ||
|
|
e17dfcb369 | ||
|
|
6d73f01107 | ||
|
|
c6c1dceaa2 | ||
|
|
59c28f10f9 | ||
|
|
f5afce8fc1 | ||
|
|
41dce06804 | ||
|
|
7382efd80d | ||
|
|
98d4443ebd | ||
|
|
ddbef1c82b | ||
|
|
52bf4381ab | ||
|
|
d8a4f44862 | ||
|
|
ed633616e7 | ||
|
|
f08f8c734b | ||
|
|
22af79a279 | ||
|
|
79f6598508 | ||
|
|
e754e6012a | ||
|
|
3227da55e0 | ||
|
|
709a79c3d9 | ||
|
|
77bb15bc8b | ||
|
|
beaa86cbf9 | ||
|
|
b8303e3860 | ||
|
|
5ba98e4411 | ||
|
|
116dfcc065 | ||
|
|
2d76927b3c | ||
|
|
41c269f208 | ||
|
|
38633d4db1 | ||
|
|
0ff45c656a | ||
|
|
bc282c6aa5 | ||
|
|
50575bc80b | ||
|
|
6f04f01c2b | ||
|
|
e80eaaa6d1 | ||
|
|
01288dafe8 | ||
|
|
e3c7b6f955 | ||
|
|
875f5a85ef | ||
|
|
ef174a1e8a | ||
|
|
958c214605 | ||
|
|
56ab9c3eee | ||
|
|
7b768db969 | ||
|
|
36c4d8552d | ||
|
|
4544fb7211 | ||
|
|
807639308a | ||
|
|
49fb634690 | ||
|
|
715efca25d | ||
|
|
a4c988c7b2 | ||
|
|
f45de05c99 | ||
|
|
d5faddbc85 | ||
|
|
5e173b92d4 | ||
|
|
574c474804 | ||
|
|
2e23149090 | ||
|
|
7be8db7832 |
9
.github/labeler-config.yml
vendored
9
.github/labeler-config.yml
vendored
@@ -9,6 +9,7 @@ Front End:
|
|||||||
- any-glob-to-any-file: 'src/main/resources/templates/**/*'
|
- any-glob-to-any-file: 'src/main/resources/templates/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/resources/static/**/*'
|
- any-glob-to-any-file: 'src/main/resources/static/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
|
||||||
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/UI/**/*'
|
||||||
|
|
||||||
Java:
|
Java:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
@@ -29,6 +30,7 @@ Security:
|
|||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java'
|
||||||
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/BackupNotFoundException.java'
|
||||||
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
||||||
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
|
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
|
||||||
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
||||||
@@ -49,12 +51,17 @@ Documentation:
|
|||||||
|
|
||||||
Docker:
|
Docker:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: '.github/workflows/build.yml'
|
||||||
|
- any-glob-to-any-file: '.github/workflows/push-docker.yml'
|
||||||
- any-glob-to-any-file: 'Dockerfile'
|
- any-glob-to-any-file: 'Dockerfile'
|
||||||
- any-glob-to-any-file: 'Dockerfile-*'
|
- any-glob-to-any-file: 'Dockerfile.*'
|
||||||
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
||||||
|
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
||||||
- any-glob-to-any-file: 'scripts/init.sh'
|
- any-glob-to-any-file: 'scripts/init.sh'
|
||||||
- any-glob-to-any-file: 'scripts/init-without-ocr.sh'
|
- any-glob-to-any-file: 'scripts/init-without-ocr.sh'
|
||||||
- any-glob-to-any-file: 'scripts/installFonts.sh'
|
- any-glob-to-any-file: 'scripts/installFonts.sh'
|
||||||
|
- any-glob-to-any-file: 'test.sh'
|
||||||
|
- any-glob-to-any-file: 'test2.sh'
|
||||||
|
|
||||||
Test:
|
Test:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
29
.github/scripts/check_language_properties.py
vendored
29
.github/scripts/check_language_properties.py
vendored
@@ -11,6 +11,8 @@ adjusting the format.
|
|||||||
Usage:
|
Usage:
|
||||||
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
|
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
|
||||||
"""
|
"""
|
||||||
|
# Sample for Windows:
|
||||||
|
# python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_de_DE.properties src\main\resources\messages_uk_UA.properties
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import glob
|
import glob
|
||||||
@@ -164,8 +166,14 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
||||||
if (
|
if (
|
||||||
basename_current_file == basename_reference_file
|
basename_current_file == basename_reference_file
|
||||||
or not file_path.startswith(
|
or (
|
||||||
os.path.join("src", "main", "resources", "messages_")
|
# only local windows command
|
||||||
|
not file_path.startswith(
|
||||||
|
os.path.join("", "src", "main", "resources", "messages_")
|
||||||
|
)
|
||||||
|
and not file_path.startswith(
|
||||||
|
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
or not file_path.endswith(".properties")
|
or not file_path.endswith(".properties")
|
||||||
or not basename_current_file.startswith("messages_")
|
or not basename_current_file.startswith("messages_")
|
||||||
@@ -275,6 +283,12 @@ if __name__ == "__main__":
|
|||||||
required=True,
|
required=True,
|
||||||
help="Branch name.",
|
help="Branch name.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--check-file",
|
||||||
|
type=str,
|
||||||
|
required=False,
|
||||||
|
help="List of changed files, separated by spaces.",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--files",
|
"--files",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
@@ -293,11 +307,14 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
file_list = args.files
|
file_list = args.files
|
||||||
if file_list is None:
|
if file_list is None:
|
||||||
file_list = glob.glob(
|
if args.check_file:
|
||||||
os.path.join(
|
file_list = [args.check_file]
|
||||||
os.getcwd(), "src", "main", "resources", "messages_*.properties"
|
else:
|
||||||
|
file_list = glob.glob(
|
||||||
|
os.path.join(
|
||||||
|
os.getcwd(), "src", "main", "resources", "messages_*.properties"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
update_missing_keys(args.reference_file, file_list)
|
update_missing_keys(args.reference_file, file_list)
|
||||||
else:
|
else:
|
||||||
check_for_differences(args.reference_file, file_list, args.branch, args.actor)
|
check_for_differences(args.reference_file, file_list, args.branch, args.actor)
|
||||||
|
|||||||
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
|
||||||
26
.github/workflows/PR-Demo-Comment.yml
vendored
26
.github/workflows/PR-Demo-Comment.yml
vendored
@@ -4,9 +4,15 @@ on:
|
|||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-comment:
|
check-comment:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
if: |
|
if: |
|
||||||
github.event.issue.pull_request &&
|
github.event.issue.pull_request &&
|
||||||
(
|
(
|
||||||
@@ -20,7 +26,8 @@ jobs:
|
|||||||
github.event.comment.user.login == 'Ludy87' ||
|
github.event.comment.user.login == 'Ludy87' ||
|
||||||
github.event.comment.user.login == 'LaserKaspar' ||
|
github.event.comment.user.login == 'LaserKaspar' ||
|
||||||
github.event.comment.user.login == 'sbplat' ||
|
github.event.comment.user.login == 'sbplat' ||
|
||||||
github.event.comment.user.login == 'reecebrowne'
|
github.event.comment.user.login == 'reecebrowne' ||
|
||||||
|
github.event.comment.user.login == 'DarioGii'
|
||||||
)
|
)
|
||||||
outputs:
|
outputs:
|
||||||
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
||||||
@@ -29,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
|
||||||
|
|
||||||
@@ -68,10 +75,13 @@ jobs:
|
|||||||
deploy-pr:
|
deploy-pr:
|
||||||
needs: check-comment
|
needs: check-comment
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: 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
|
||||||
|
|
||||||
@@ -85,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
|
||||||
@@ -98,7 +108,9 @@ 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: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||||
@@ -107,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
|
||||||
|
|||||||
9
.github/workflows/PR-Demo-cleanup.yml
vendored
9
.github/workflows/PR-Demo-cleanup.yml
vendored
@@ -4,11 +4,12 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, closed]
|
types: [opened, synchronize, reopened, closed]
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
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:
|
||||||
@@ -20,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
|
||||||
|
|
||||||
|
|||||||
5
.github/workflows/auto-labeler.yml
vendored
5
.github/workflows/auto-labeler.yml
vendored
@@ -3,7 +3,8 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
@@ -12,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
|
||||||
|
|
||||||
|
|||||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -6,13 +6,15 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
actions: read
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@@ -22,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
|
||||||
|
|
||||||
@@ -44,7 +46,18 @@ jobs:
|
|||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: true
|
DOCKER_ENABLE_SECURITY: true
|
||||||
|
|
||||||
|
- name: Upload Test Reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
with:
|
||||||
|
name: test-reports-jdk-${{ matrix.jdk-version }}
|
||||||
|
path: |
|
||||||
|
build/reports/tests/
|
||||||
|
build/test-results/
|
||||||
|
build/reports/problems/
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
docker-compose-tests:
|
docker-compose-tests:
|
||||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
||||||
# (github.event_name == 'pull_request' &&
|
# (github.event_name == 'pull_request' &&
|
||||||
@@ -64,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
|
||||||
|
|
||||||
@@ -82,19 +95,21 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Docker Compose
|
- name: Install Docker Compose
|
||||||
run: |
|
run: |
|
||||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
sudo chmod +x /usr/local/bin/docker-compose
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
cache: 'pip' # caching pip dependencies
|
||||||
|
|
||||||
- name: Pip requirements
|
- name: Pip requirements
|
||||||
run: |
|
run: |
|
||||||
pip install -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
|
||||||
|
|||||||
175
.github/workflows/check_properties.yml
vendored
175
.github/workflows/check_properties.yml
vendored
@@ -8,15 +8,17 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # Allow read access to repository content
|
contents: read # Allow read access to repository content
|
||||||
issues: write # Allow posting comments on issues/PRs
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-files:
|
check-files:
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write # Allow posting comments on issues/PRs
|
||||||
|
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
|
||||||
|
|
||||||
@@ -26,7 +28,28 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Get PR data
|
||||||
|
id: get-pr-data
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const repoOwner = context.payload.repository.owner.login;
|
||||||
|
const repoName = context.payload.repository.name;
|
||||||
|
const branch = context.payload.pull_request.head.ref;
|
||||||
|
|
||||||
|
console.log(`PR Number: ${prNumber}`);
|
||||||
|
console.log(`Repo Owner: ${repoOwner}`);
|
||||||
|
console.log(`Repo Name: ${repoName}`);
|
||||||
|
console.log(`Branch: ${branch}`);
|
||||||
|
|
||||||
|
core.setOutput("pr_number", prNumber);
|
||||||
|
core.setOutput("repo_owner", repoOwner);
|
||||||
|
core.setOutput("repo_name", repoName);
|
||||||
|
core.setOutput("branch", branch);
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Fetch PR changed files
|
- name: Fetch PR changed files
|
||||||
id: fetch-pr-changes
|
id: fetch-pr-changes
|
||||||
@@ -34,55 +57,114 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "Fetching PR changed files..."
|
echo "Fetching PR changed files..."
|
||||||
|
|
||||||
gh repo set-default ${{ github.event.pull_request.head.repo.full_name }} # Set the fork repository as default
|
|
||||||
|
|
||||||
# Fetch the list of changed files in the PR
|
|
||||||
echo "Getting list of changed files from PR..."
|
echo "Getting list of changed files from PR..."
|
||||||
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
|
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
|
||||||
|
|
||||||
- name: Determine reference file
|
- name: Determine reference file test
|
||||||
id: determine-file
|
id: determine-file
|
||||||
env:
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
run: |
|
script: |
|
||||||
echo "Determining reference file..."
|
const fs = require("fs");
|
||||||
REPO_OWNER=$(gh pr view ${{ github.event.pull_request.number }} --json author -q '.author.login') # Get PR author's username
|
const path = require("path");
|
||||||
REPO_NAME=$(gh pr view ${{ github.event.pull_request.number }} --json headRepository -q '.headRepository.name') # Get PR repository name
|
|
||||||
BRANCH=$(gh pr view ${{ github.event.pull_request.number }} --json headRefName -q '.headRefName') # Get PR branch name
|
|
||||||
|
|
||||||
mkdir -p pr-branch # Create a directory for PR files
|
const prNumber = ${{ steps.get-pr-data.outputs.pr_number }};
|
||||||
|
const repoOwner = "${{ steps.get-pr-data.outputs.repo_owner }}";
|
||||||
|
const repoName = "${{ steps.get-pr-data.outputs.repo_name }}";
|
||||||
|
|
||||||
# Download the content of each changed file
|
const prRepoOwner = "${{ github.event.pull_request.head.repo.owner.login }}";
|
||||||
while IFS= read -r file; do
|
const prRepoName = "${{ github.event.pull_request.head.repo.name }}";
|
||||||
mkdir -p "pr-branch/$(dirname "$file")" # Create directories for files
|
const branch = "${{ steps.get-pr-data.outputs.branch }}";
|
||||||
gh api repos/$REPO_OWNER/$REPO_NAME/contents/$file?ref=$BRANCH --jq '.content' | base64 -d > "pr-branch/src/main/resources/$(basename "$file")" # Save decoded file content
|
|
||||||
done < changed_files.txt
|
|
||||||
|
|
||||||
# Generate a list of files without the "pr-branch/" prefix
|
console.log(`Determining reference file for PR #${prNumber}`);
|
||||||
find pr-branch/ -type f | awk -F'pr-branch/' '{print $2}' > file_list.txt
|
|
||||||
|
|
||||||
mapfile -t FILES_LIST < file_list.txt # Read the file list into an array
|
// Validate inputs
|
||||||
FILES_LIST_STR="${FILES_LIST[*]}" # Join array into a space-separated string
|
const validateInput = (input, regex, name) => {
|
||||||
echo "FILES_LIST=${FILES_LIST_STR}" >> $GITHUB_ENV # Export the file list to the environment
|
if (!regex.test(input)) {
|
||||||
echo "Changed files: ${FILES_LIST_STR}"
|
throw new Error(`Invalid ${name}: ${input}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
cat file_list.txt # Display the file list
|
validateInput(repoOwner, /^[a-zA-Z0-9_-]+$/, "repository owner");
|
||||||
|
validateInput(repoName, /^[a-zA-Z0-9._-]+$/, "repository name");
|
||||||
|
validateInput(branch, /^[a-zA-Z0-9._/-]+$/, "branch name");
|
||||||
|
|
||||||
# Determine which reference file to use
|
// Get the list of changed files in the PR
|
||||||
if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then
|
const { data: files } = await github.rest.pulls.listFiles({
|
||||||
echo "Using PR branch reference file"
|
owner: repoOwner,
|
||||||
REFERENCE_FILE="pr-branch-messages_en_GB.properties"
|
repo: repoName,
|
||||||
gh api repos/$REPO_OWNER/$REPO_NAME/contents/src/main/resources/messages_en_GB.properties?ref=${{ github.event.pull_request.head.ref }} \
|
pull_number: prNumber,
|
||||||
--jq '.content' | base64 -d > $REFERENCE_FILE # Save PR branch reference file
|
});
|
||||||
else
|
|
||||||
echo "Using main branch reference file"
|
|
||||||
REFERENCE_FILE="main-branch-messages_en_GB.properties"
|
|
||||||
gh api repos/Ludy87/test_java/contents/src/main/resources/messages_en_GB.properties?ref=main \
|
|
||||||
--jq '.content' | base64 -d > $REFERENCE_FILE # Save main branch reference file
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "REFERENCE_FILE=$REFERENCE_FILE" >> $GITHUB_ENV # Export reference file path to the environment
|
// Filter for relevant files based on the PR changes
|
||||||
|
const changedFiles = files
|
||||||
|
.map(file => file.filename)
|
||||||
|
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]+\.properties$/.test(file));
|
||||||
|
|
||||||
|
console.log("Changed files:", changedFiles);
|
||||||
|
|
||||||
|
// Create a temporary directory for PR files
|
||||||
|
const tempDir = "pr-branch";
|
||||||
|
if (!fs.existsSync(tempDir)) {
|
||||||
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and save each changed file
|
||||||
|
for (const file of changedFiles) {
|
||||||
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
|
owner: prRepoOwner,
|
||||||
|
repo: prRepoName,
|
||||||
|
path: file,
|
||||||
|
ref: branch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
|
||||||
|
const filePath = path.join(tempDir, file);
|
||||||
|
const dirPath = path.dirname(filePath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, content);
|
||||||
|
console.log(`Saved file: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the list of changed files for further processing
|
||||||
|
const fileList = changedFiles.join(" ");
|
||||||
|
core.exportVariable("FILES_LIST", fileList);
|
||||||
|
console.log("Files saved and listed in FILES_LIST.");
|
||||||
|
|
||||||
|
// Determine reference file
|
||||||
|
let referenceFilePath;
|
||||||
|
if (changedFiles.includes("src/main/resources/messages_en_GB.properties")) {
|
||||||
|
console.log("Using PR branch reference file.");
|
||||||
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
|
owner: prRepoOwner,
|
||||||
|
repo: prRepoName,
|
||||||
|
path: "src/main/resources/messages_en_GB.properties",
|
||||||
|
ref: branch,
|
||||||
|
});
|
||||||
|
|
||||||
|
referenceFilePath = "pr-branch-messages_en_GB.properties";
|
||||||
|
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
|
||||||
|
fs.writeFileSync(referenceFilePath, content);
|
||||||
|
} else {
|
||||||
|
console.log("Using main branch reference file.");
|
||||||
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
|
owner: repoOwner,
|
||||||
|
repo: repoName,
|
||||||
|
path: "src/main/resources/messages_en_GB.properties",
|
||||||
|
ref: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
referenceFilePath = "main-branch-messages_en_GB.properties";
|
||||||
|
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
|
||||||
|
fs.writeFileSync(referenceFilePath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Reference file path: ${referenceFilePath}`);
|
||||||
|
core.exportVariable("REFERENCE_FILE", referenceFilePath);
|
||||||
|
|
||||||
- name: Run Python script to check files
|
- name: Run Python script to check files
|
||||||
id: run-check
|
id: run-check
|
||||||
@@ -92,7 +174,8 @@ jobs:
|
|||||||
--actor ${{ github.event.pull_request.user.login }} \
|
--actor ${{ github.event.pull_request.user.login }} \
|
||||||
--reference-file "${REFERENCE_FILE}" \
|
--reference-file "${REFERENCE_FILE}" \
|
||||||
--branch "pr-branch" \
|
--branch "pr-branch" \
|
||||||
--files "${FILES_LIST[@]}" > result.txt || true
|
--files "${FILES_LIST[@]}" > result.txt
|
||||||
|
continue-on-error: true # Continue the job even if this step fails
|
||||||
|
|
||||||
- name: Capture output
|
- name: Capture output
|
||||||
id: capture-output
|
id: capture-output
|
||||||
@@ -124,13 +207,13 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
|
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
|
||||||
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||||
const prNumber = context.issue.number;
|
const issueNumber = context.issue.number;
|
||||||
|
|
||||||
// Find existing comment
|
// Find existing comment
|
||||||
const comments = await github.rest.issues.listComments({
|
const comments = await github.rest.issues.listComments({
|
||||||
owner: repoOwner,
|
owner: repoOwner,
|
||||||
repo: repoName,
|
repo: repoName,
|
||||||
issue_number: prNumber
|
issue_number: issueNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
||||||
@@ -152,7 +235,7 @@ jobs:
|
|||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
owner: repoOwner,
|
owner: repoOwner,
|
||||||
repo: repoName,
|
repo: repoName,
|
||||||
issue_number: prNumber,
|
issue_number: issueNumber,
|
||||||
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
|
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
|
||||||
});
|
});
|
||||||
console.log("Created new comment.");
|
console.log("Created new comment.");
|
||||||
|
|||||||
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
|
||||||
|
|||||||
42
.github/workflows/licenses-update.yml
vendored
42
.github/workflows/licenses-update.yml
vendored
@@ -7,7 +7,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "build.gradle"
|
- "build.gradle"
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
generate-license-report:
|
generate-license-report:
|
||||||
@@ -17,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: ${{ secrets.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
|
||||||
|
|
||||||
@@ -41,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: |
|
||||||
@@ -52,34 +60,24 @@ jobs:
|
|||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
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
|
|
||||||
|
|||||||
5
.github/workflows/manage-label.yml
vendored
5
.github/workflows/manage-label.yml
vendored
@@ -4,7 +4,8 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "30 20 * * *"
|
- cron: "30 20 * * *"
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
@@ -14,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
|
||||||
|
|
||||||
|
|||||||
281
.github/workflows/multiOSReleases.yml
vendored
281
.github/workflows/multiOSReleases.yml
vendored
@@ -5,30 +5,53 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-installers:
|
read_versions:
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
matrix:
|
outputs:
|
||||||
include:
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
- os: windows-latest
|
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||||
platform: win
|
|
||||||
ext: exe
|
|
||||||
#- os: macos-latest
|
|
||||||
# platform: mac
|
|
||||||
# ext: dmg
|
|
||||||
#- os: ubuntu-latest
|
|
||||||
# platform: linux
|
|
||||||
# ext: deb
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
permissions:
|
|
||||||
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:
|
||||||
|
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:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -42,7 +65,95 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.7
|
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:
|
||||||
|
needs: read_versions
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win-
|
||||||
|
# - os: macos-latest
|
||||||
|
# platform: mac-
|
||||||
|
# - os: ubuntu-latest
|
||||||
|
# platform: linux-
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
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
|
||||||
|
|
||||||
# Install Windows dependencies
|
# Install Windows dependencies
|
||||||
- name: Install WiX Toolset
|
- name: Install WiX Toolset
|
||||||
@@ -51,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
|
||||||
@@ -81,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/*
|
||||||
|
|||||||
22
.github/workflows/pre_commit.yml
vendored
22
.github/workflows/pre_commit.yml
vendored
@@ -1,29 +1,37 @@
|
|||||||
name: Pre-commit
|
name: Pre-commit
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
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:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
|
cache: 'pip' # caching pip dependencies
|
||||||
- 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: |
|
||||||
@@ -35,7 +43,7 @@ jobs:
|
|||||||
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
|
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
|
||||||
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
|
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: "ci: 🤖 format everything with pre-commit"
|
commit-message: "ci: 🤖 format everything with pre-commit"
|
||||||
|
|||||||
19
.github/workflows/push-docker.yml
vendored
19
.github/workflows/push-docker.yml
vendored
@@ -9,17 +9,16 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: 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
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.7
|
gradle-version: 8.12
|
||||||
|
|
||||||
- name: Run Gradle Command
|
- name: Run Gradle Command
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
@@ -42,9 +41,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@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
|
||||||
@@ -68,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
|
||||||
@@ -90,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: .
|
||||||
@@ -135,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: .
|
||||||
@@ -166,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 }}
|
||||||
|
|||||||
168
.github/workflows/releaseArtifacts.yml
vendored
168
.github/workflows/releaseArtifacts.yml
vendored
@@ -5,14 +5,12 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
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]
|
||||||
@@ -21,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
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.7
|
gradle-version: 8.12
|
||||||
|
|
||||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
@@ -47,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
|
||||||
|
|||||||
7
.github/workflows/stale.yml
vendored
7
.github/workflows/stale.yml
vendored
@@ -5,7 +5,8 @@ on:
|
|||||||
- cron: "30 0 * * *"
|
- cron: "30 0 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
@@ -15,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
|
||||||
|
|
||||||
@@ -36,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
|
||||||
|
|||||||
5
.github/workflows/swagger.yml
vendored
5
.github/workflows/swagger.yml
vendored
@@ -6,14 +6,15 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
12
.github/workflows/sync_files.yml
vendored
12
.github/workflows/sync_files.yml
vendored
@@ -9,7 +9,8 @@ on:
|
|||||||
- "src/main/resources/messages_*.properties"
|
- "src/main/resources/messages_*.properties"
|
||||||
- "scripts/ignore_translation.toml"
|
- "scripts/ignore_translation.toml"
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-readme:
|
sync-readme:
|
||||||
@@ -19,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
|
||||||
|
|
||||||
@@ -27,9 +28,10 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.12"
|
||||||
|
cache: 'pip' # caching pip dependencies
|
||||||
- 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
|
||||||
@@ -42,7 +44,7 @@ jobs:
|
|||||||
git diff --staged --quiet || git commit -m ":memo: Sync README
|
git diff --staged --quiet || git commit -m ":memo: Sync README
|
||||||
> Made via sync_files.yml" || echo "no changes"
|
> Made via sync_files.yml" || echo "no changes"
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: Update files
|
commit-message: Update files
|
||||||
|
|||||||
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
|
||||||
10
.github/workflows/update-translations.yml
vendored
10
.github/workflows/update-translations.yml
vendored
@@ -1,12 +1,14 @@
|
|||||||
name: Update Translations
|
name: Update Translations
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- "src/main/resources/messages_en_GB.properties"
|
- "src/main/resources/messages_en_GB.properties"
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-translations-main:
|
update-translations-main:
|
||||||
@@ -17,7 +19,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
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Run Python script to check files
|
- name: Run Python script to check files
|
||||||
id: run-check
|
id: run-check
|
||||||
@@ -50,7 +52,7 @@ jobs:
|
|||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: "Update translation files"
|
commit-message: "Update translation files"
|
||||||
|
|||||||
29
.gitignore
vendored
29
.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
|
||||||
@@ -146,19 +151,37 @@ out/
|
|||||||
# cucumber
|
# cucumber
|
||||||
/cucumber/reports/**
|
/cucumber/reports/**
|
||||||
|
|
||||||
# Certs
|
# Certs and Security Files
|
||||||
*.p12
|
*.p12
|
||||||
|
*.pk8
|
||||||
*.pem
|
*.pem
|
||||||
*.crt
|
*.crt
|
||||||
*.cer
|
*.cer
|
||||||
|
*.cert
|
||||||
*.der
|
*.der
|
||||||
*.key
|
*.key
|
||||||
*.csr
|
*.csr
|
||||||
|
*.kdbx
|
||||||
|
*.jks
|
||||||
|
*.asc
|
||||||
|
|
||||||
|
# SSH Keys
|
||||||
|
*.pub
|
||||||
|
*.priv
|
||||||
|
id_rsa
|
||||||
|
id_rsa.pub
|
||||||
|
id_ecdsa
|
||||||
|
id_ecdsa.pub
|
||||||
|
id_ed25519
|
||||||
|
id_ed25519.pub
|
||||||
|
.ssh/
|
||||||
|
*ssh
|
||||||
|
|
||||||
# cache
|
# cache
|
||||||
|
.cache
|
||||||
.ruff_cache
|
.ruff_cache
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
**/jcef-bundle/
|
**/jcef-bundle/
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ Documentation for Stirling-PDF is handled in a separate repository. Please see [
|
|||||||
|
|
||||||
First, make sure you've read the section [Pull Requests](#pull-requests).
|
First, make sure you've read the section [Pull Requests](#pull-requests).
|
||||||
|
|
||||||
To build from the source, please follow this [Guide](LocalRunGuide.md).
|
|
||||||
|
|
||||||
If, at any point in time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
|
If, at any point in time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
|
||||||
|
|
||||||
|
Developers should review our [Developer Guide](DeveloperGuide.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
||||||
|
|||||||
@@ -575,3 +575,42 @@ In your Thymeleaf templates, use the `#{key}` syntax to reference the new transl
|
|||||||
```
|
```
|
||||||
|
|
||||||
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
||||||
|
|
||||||
|
|
||||||
|
## Managing Dependencies
|
||||||
|
|
||||||
|
When adding new dependencies or updating existing ones in Stirling-PDF, follow these steps to ensure proper verification and security:
|
||||||
|
|
||||||
|
1. Update the dependency in `build.gradle`:
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
// Add or update your dependency
|
||||||
|
implementation "com.example:new-library:1.2.3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Generate new verification metadata and keys:
|
||||||
|
```bash
|
||||||
|
# Generate verification metadata with signatures and checksums
|
||||||
|
./gradlew clean dependencies buildEnvironment --write-verification-metadata sha256,pgp
|
||||||
|
|
||||||
|
# Export the .keys file
|
||||||
|
./gradlew --export-keys
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Files to commit:
|
||||||
|
- `build.gradle` - Your dependency changes
|
||||||
|
- `gradle/verification-metadata.xml` - Contains verification rules and checksums
|
||||||
|
- `gradle/verification-keyring.keys` - Contains PGP keys in text format
|
||||||
|
|
||||||
|
4. Verify the build works with the new verification:
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Before committing, check:
|
||||||
|
- Verify any new BOM files are properly handled in verification metadata
|
||||||
|
- Review the changes in `verification-metadata.xml` to ensure they match your dependency updates
|
||||||
|
|
||||||
|
This ensures dependencies are properly verified and secure while maintaining transparency in the repository.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build the application
|
# Build the application
|
||||||
FROM gradle:8.11-jdk17 AS build
|
FROM gradle:8.12-jdk17 AS build
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | qpdf | Java | Javascript | Unoconv | tesseract |
|
|
||||||
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
|
|
||||||
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
|
|
||||||
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| crop | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| extract-page | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ | | |
|
|
||||||
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| remove-pages | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| scale-pages | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| split-pdfs | ✔️ | | | | | | | | | ✔️ | | | |
|
|
||||||
| file-to-pdf | | ✔️ | | | ✔️ | ✔️ | | ✔️ | | | | ✔️ | |
|
|
||||||
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | |
|
|
||||||
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
|
||||||
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | | | |
|
|
||||||
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | |
|
|
||||||
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | |
|
|
||||||
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
|
||||||
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
|
||||||
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
|
||||||
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
|
||||||
| add-password | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| add-watermark | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| cert-sign | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| remove-cert-sign | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| change-permissions | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| remove-password | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | | | |
|
|
||||||
| add-image | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| compare | | | | ✔️ | | | | | | | ✔️ | | |
|
|
||||||
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
|
|
||||||
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
|
||||||
| extract-images | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| flatten | | | | ✔️ | | | | | | | ✔️ | | |
|
|
||||||
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
|
|
||||||
| ocr-pdf | | | | ✔️ | ✔️ | | | | | | | | ✔ |
|
|
||||||
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
|
||||||
| repair | | | | ✔️ | ✔️ | | | ✔️ | ✔ | | | | |
|
|
||||||
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
|
|
||||||
| sign | | | | ✔️ | | | | | | | ✔️ | | |
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
## User Guide for Local Directory Scanning and File Processing
|
|
||||||
|
|
||||||
### Setting Up Watched Folders
|
|
||||||
|
|
||||||
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
|
|
||||||
- The default directory for this is `./pipeline/watchedFolders/`.
|
|
||||||
- Place any directories you want to be scanned into this folder. This folder should contain multiple folders, each for their own tasks and pipelines.
|
|
||||||
|
|
||||||
### Configuring Processing with JSON Files
|
|
||||||
|
|
||||||
- In each directory you want processed (e.g., `./pipeline/watchedFolders/officePrinter`), include a JSON configuration file.
|
|
||||||
- This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them). This can be made, configured, and downloaded from the Stirling-PDF Pipeline interface.
|
|
||||||
|
|
||||||
### Automatic Scanning and Processing
|
|
||||||
|
|
||||||
- The system automatically checks the watched folder every minute for new directories and files to process.
|
|
||||||
- When a directory with a valid JSON configuration file is found, it begins processing the files inside according to the configuration.
|
|
||||||
|
|
||||||
### Processing Steps
|
|
||||||
|
|
||||||
- Files in each directory are processed according to the instructions in the JSON file.
|
|
||||||
- This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to the next process.
|
|
||||||
|
|
||||||
### Results and Output
|
|
||||||
|
|
||||||
- After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location `./pipeline/finishedFolders/`.
|
|
||||||
- Each processed file is named and organized according to the rules set in the JSON configuration.
|
|
||||||
|
|
||||||
### Completion and Cleanup
|
|
||||||
|
|
||||||
- Once processing is complete, the original files in the watched folder's directory are removed.
|
|
||||||
- You can find the processed files in the designated output location.
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
- If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.
|
|
||||||
|
|
||||||
### User Interaction
|
|
||||||
|
|
||||||
- As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.
|
|
||||||
- The system handles the rest, including scanning, processing, and outputting results.
|
|
||||||
@@ -60,3 +60,13 @@ ignore = [
|
|||||||
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
|
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
|
||||||
|
|
||||||
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.
|
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.
|
||||||
|
|
||||||
|
### Use this code to perform a local check
|
||||||
|
|
||||||
|
#### Windows command
|
||||||
|
|
||||||
|
```ps
|
||||||
|
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_pl_PL.properties
|
||||||
|
|
||||||
|
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --check-file src\main\resources\messages_pl_PL.properties
|
||||||
|
```
|
||||||
|
|||||||
327
LocalRunGuide.md
327
LocalRunGuide.md
@@ -1,327 +0,0 @@
|
|||||||
To run the application without Docker/Podman, you will need to manually install all dependencies and build the necessary components.
|
|
||||||
|
|
||||||
Note that some dependencies might not be available in the standard repositories of all Linux distributions, and may require additional steps to install.
|
|
||||||
|
|
||||||
The following guide assumes you have a basic understanding of using a command line interface in your operating system.
|
|
||||||
|
|
||||||
It should work on most Linux distributions and MacOS. For Windows, you might need to use Windows Subsystem for Linux (WSL) for certain steps. The amount of dependencies is to actually reduce overall size, i.e., installing LibreOffice subcomponents rather than the full LibreOffice package.
|
|
||||||
|
|
||||||
You could theoretically use a Distrobox/Toolbox if your distribution has old or not all packages. But you might just as well use the Docker container then.
|
|
||||||
|
|
||||||
### Step 1: Prerequisites
|
|
||||||
|
|
||||||
Install the following software, if not already installed:
|
|
||||||
|
|
||||||
- Java 17 or later (21 recommended)
|
|
||||||
- Gradle 7.0 or later (included within the repo, so not needed on the server)
|
|
||||||
- Git
|
|
||||||
- Python 3.8 (with pip)
|
|
||||||
- Make
|
|
||||||
- GCC/G++
|
|
||||||
- Automake
|
|
||||||
- Autoconf
|
|
||||||
- libtool
|
|
||||||
- pkg-config
|
|
||||||
- zlib1g-dev
|
|
||||||
- libleptonica-dev
|
|
||||||
|
|
||||||
For Debian-based systems, you can use the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ openjdk-21-jdk python3 python3-pip
|
|
||||||
```
|
|
||||||
|
|
||||||
For Fedora-based systems, use this command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-21-openjdk python3 python3-pip
|
|
||||||
```
|
|
||||||
|
|
||||||
For non-root users with Nix Package Manager, use the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-channel --update
|
|
||||||
nix-env -iA nixpkgs.jdk21 nixpkgs.git nixpkgs.python38 nixpkgs.gnumake nixpkgs.libgcc nixpkgs.automake nixpkgs.autoconf nixpkgs.libtool nixpkgs.pkg-config nixpkgs.zlib nixpkgs.leptonica
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Clone and Build jbig2enc (Only required for certain OCR functionality)
|
|
||||||
|
|
||||||
For Debian and Fedora, you can build it from source using the following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir ~/.git
|
|
||||||
cd ~/.git && \
|
|
||||||
git clone https://github.com/agl/jbig2enc.git && \
|
|
||||||
cd jbig2enc && \
|
|
||||||
./autogen.sh && \
|
|
||||||
./configure && \
|
|
||||||
make && \
|
|
||||||
sudo make install
|
|
||||||
```
|
|
||||||
|
|
||||||
For Nix, you will face `Leptonica not detected`. Bypass this by installing it directly using the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-env -iA nixpkgs.jbig2enc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Install Additional Software
|
|
||||||
|
|
||||||
Next, we need to install LibreOffice for conversions, qpdf for OCR, and OpenCV for pattern recognition functionality.
|
|
||||||
|
|
||||||
Install the following software:
|
|
||||||
|
|
||||||
- libreoffice-core
|
|
||||||
- libreoffice-common
|
|
||||||
- libreoffice-writer
|
|
||||||
- libreoffice-calc
|
|
||||||
- libreoffice-impress
|
|
||||||
- python3-uno
|
|
||||||
- unoconv
|
|
||||||
- pngquant
|
|
||||||
- unpaper
|
|
||||||
- qpdf
|
|
||||||
- opencv-python-headless
|
|
||||||
|
|
||||||
For Debian-based systems, you can use the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
|
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
|
|
||||||
```
|
|
||||||
|
|
||||||
For Fedora:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
|
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
|
||||||
```
|
|
||||||
|
|
||||||
For Nix:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.qpdf nixpkgs.poppler_utils
|
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Clone and Build Stirling-PDF
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ~/.git && \
|
|
||||||
git clone https://github.com/Stirling-Tools/Stirling-PDF.git && \
|
|
||||||
cd Stirling-PDF && \
|
|
||||||
chmod +x ./gradlew && \
|
|
||||||
./gradlew build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Move Jar to Desired Location
|
|
||||||
|
|
||||||
After the build process, a `.jar` file will be generated in the `build/libs` directory. You can move this file to a desired location, for example, `/opt/Stirling-PDF/`. You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory. This folder is required for the Python scripts using OpenCV.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo mkdir /opt/Stirling-PDF && \
|
|
||||||
sudo mv ./build/libs/Stirling-PDF-*.jar /opt/Stirling-PDF/ && \
|
|
||||||
sudo mv scripts /opt/Stirling-PDF/ && \
|
|
||||||
echo "Scripts installed."
|
|
||||||
```
|
|
||||||
|
|
||||||
For non-root users, you can just keep the jar in the main directory of Stirling-PDF using the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mv ./build/libs/Stirling-PDF-*.jar ./Stirling-PDF-*.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Other Files
|
|
||||||
|
|
||||||
#### OCR
|
|
||||||
|
|
||||||
If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running non-English scanning.
|
|
||||||
|
|
||||||
##### Installing Language Packs
|
|
||||||
|
|
||||||
The easiest method is to use the language packs provided by your repositories. Skip the other steps if they are available.
|
|
||||||
|
|
||||||
**Manual:**
|
|
||||||
|
|
||||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
|
||||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
|
||||||
|
|
||||||
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
|
|
||||||
|
|
||||||
**Debian-based systems**, install languages with this command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt update && \
|
|
||||||
# All languages
|
|
||||||
# sudo apt install -y 'tesseract-ocr-*'
|
|
||||||
|
|
||||||
# Find languages:
|
|
||||||
apt search tesseract-ocr-
|
|
||||||
|
|
||||||
# View installed languages:
|
|
||||||
dpkg-query -W tesseract-ocr- | sed 's/tesseract-ocr-//g'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Fedora:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# All languages
|
|
||||||
# sudo dnf install -y tesseract-langpack-*
|
|
||||||
|
|
||||||
# Find languages:
|
|
||||||
dnf search -C tesseract-langpack-
|
|
||||||
|
|
||||||
# View installed languages:
|
|
||||||
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Nix:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-env -iA nixpkgs.tesseract
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Nix Package Manager pre-installs almost all the language packs when Tesseract is installed.
|
|
||||||
|
|
||||||
### Step 7: Run Stirling-PDF
|
|
||||||
|
|
||||||
Those who have pushed to the root directory, run the following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew bootRun
|
|
||||||
or
|
|
||||||
java -jar /opt/Stirling-PDF/Stirling-PDF-*.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
Since LibreOffice, soffice, and conversion tools have their dbus_tmp_dir set as `dbus_tmp_dir="/run/user/$(id -u)/libreoffice-dbus"`, you might get the following error when using their endpoints:
|
|
||||||
|
|
||||||
```
|
|
||||||
[Thread-7] INFO s.s.SPDF.utils.ProcessExecutor - mkdir: cannot create directory ‘/run/user/1501’: Permission denied
|
|
||||||
```
|
|
||||||
|
|
||||||
To resolve this, before starting Stirling-PDF, you have to set the environment variable to a directory you have write access to by using the following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir temp
|
|
||||||
export DBUS_SESSION_BUS_ADDRESS="unix:path=./temp"
|
|
||||||
./gradlew bootRun
|
|
||||||
or
|
|
||||||
java -jar ./Stirling-PDF-*.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 8: Adding a Desktop Icon
|
|
||||||
|
|
||||||
This will add a modified app starter to your app menu.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
location=$(pwd)/gradlew
|
|
||||||
image=$(pwd)/docs/stirling-transparent.svg
|
|
||||||
|
|
||||||
cat > ~/.local/share/applications/Stirling-PDF.desktop <<EOF
|
|
||||||
[Desktop Entry]
|
|
||||||
Name=Stirling PDF;
|
|
||||||
GenericName=Launch StirlingPDF and open its WebGUI;
|
|
||||||
Category=Office;
|
|
||||||
Exec=xdg-open http://localhost:8080 && nohup $location bootRun &;
|
|
||||||
Icon=$image;
|
|
||||||
Keywords=pdf;
|
|
||||||
Type=Application;
|
|
||||||
NoDisplay=false;
|
|
||||||
Terminal=true;
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Currently, the app will run in the background until it is manually closed.
|
|
||||||
|
|
||||||
### Optional: Changing the Host and Port of the Application
|
|
||||||
|
|
||||||
To override the default configuration, you can add the following to `/.git/Stirling-PDF/configs/custom_settings.yml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
host: 0.0.0.0 # Not working - use instead address
|
|
||||||
address: 0.0.0.0
|
|
||||||
port: 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
`-Djava.net.preferIPv4Stack=true` --> To force IPv4 only in the Java starting command
|
|
||||||
|
|
||||||
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
|
||||||
|
|
||||||
### Optional: Run Stirling-PDF as a Service (requires root)
|
|
||||||
|
|
||||||
First, create a `.env` file, where you can store environment variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
touch /opt/Stirling-PDF/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
In this file, you can add all variables, one variable per line, as stated in the main readme (for example `SYSTEM_DEFAULTLOCALE="de-DE"`).
|
|
||||||
|
|
||||||
Create a new file where we store our service settings and open it with the nano editor:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nano /etc/systemd/system/stirlingpdf.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Paste this content, and make sure to update the filename of the jar file. Press `Ctrl+S` and `Ctrl+X` to save and exit the nano editor:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[Unit]
|
|
||||||
Description=Stirling-PDF service
|
|
||||||
After=syslog.target network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
SuccessExitStatus=143
|
|
||||||
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
|
|
||||||
Type=simple
|
|
||||||
|
|
||||||
EnvironmentFile=/opt/Stirling-PDF/.env
|
|
||||||
WorkingDirectory=/opt/Stirling-PDF
|
|
||||||
ExecStart=/usr/bin/java -jar Stirling-PDF-0.17.2.jar
|
|
||||||
ExecStop=/bin/kill -15 $MAINPID
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Notify systemd that it has to rebuild its internal service database (you have to run this command every time you make a change in the service file):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
Enable the service to tell it to start automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl enable stirlingpdf.service
|
|
||||||
```
|
|
||||||
|
|
||||||
See the status of the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl status stirlingpdf.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Manually start/stop/restart the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl start stirlingpdf.service
|
|
||||||
sudo systemctl stop stirlingpdf.service
|
|
||||||
sudo systemctl restart stirlingpdf.service
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Remember to set the necessary environment variables before running the project if you want to customize the application. The list can be seen in the main readme.
|
|
||||||
|
|
||||||
You can do this in the terminal by using the `export` command or `-D` argument to the Java `-jar` command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export APP_HOME_NAME="Stirling PDF"
|
|
||||||
or
|
|
||||||
-DAPP_HOME_NAME="Stirling PDF"
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Pipeline Configuration and Usage Tutorial
|
|
||||||
|
|
||||||
- Configure the pipeline config file and input files to run files against it.
|
|
||||||
- For reuse, download the config file and re-upload it when needed, or place it in `/pipeline/defaultWebUIConfigs/` to auto-load in the web UI for all users.
|
|
||||||
|
|
||||||
## Steps to Configure and Use Your Pipeline
|
|
||||||
|
|
||||||
1. **Access Configuration**
|
|
||||||
- Upon entering the screen, click on the **Configure** button.
|
|
||||||
|
|
||||||
2. **Enter Pipeline Name**
|
|
||||||
- Provide a name for your pipeline in the designated field.
|
|
||||||
|
|
||||||
3. **Select Operations**
|
|
||||||
- Choose the operations for your pipeline (e.g., **Split Pages**), then click **Add Operation**.
|
|
||||||
|
|
||||||
4. **Configure Operation Settings**
|
|
||||||
- Input the necessary settings for each added operation. Settings are highlighted in yellow if customization is needed.
|
|
||||||
|
|
||||||
5. **Add More Operations**
|
|
||||||
- You can add and adjust the order of multiple operations. Ensure each operation's settings are customized.
|
|
||||||
|
|
||||||
6. **Save Settings**
|
|
||||||
- Click **Save Operation Settings** after customizing settings for each operation.
|
|
||||||
|
|
||||||
7. **Validate Pipeline**
|
|
||||||
- Use the **Validation** button to check your pipeline. A green indicator signifies correct setup; a pop-out error indicates issues.
|
|
||||||
|
|
||||||
8. **Download Pipeline Configuration**
|
|
||||||
- To use the configuration for folder scanning (or save it for future use and re-upload it), download a JSON file in this menu. You can also pre-load it for future use by placing it in `/pipeline/defaultWebUIConfigs/`. It will then appear in the dropdown menu for all users to use.
|
|
||||||
|
|
||||||
9. **Submit Files for Processing**
|
|
||||||
- If your pipeline is correctly set up, close the configure menu, input the files, and hit **Submit**.
|
|
||||||
|
|
||||||
10. **Note on Web UI Limitations**
|
|
||||||
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.
|
|
||||||
|
|
||||||
### Current Limitations
|
|
||||||
|
|
||||||
- Cannot have more than one of the same operation.
|
|
||||||
- Cannot input additional files via UI.
|
|
||||||
- All files and operations run in serial mode.
|
|
||||||
373
README.md
373
README.md
@@ -4,6 +4,7 @@
|
|||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
[](https://discord.gg/HYmhKj45pU)
|
[](https://discord.gg/HYmhKj45pU)
|
||||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||||
|
[](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
|
||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/stirling-pdf?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-stirling-pdf" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=641239&theme=light" alt="Stirling PDF - Open source locally hosted web PDF editor | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/stirling-pdf?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-stirling-pdf" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=641239&theme=light" alt="Stirling PDF - Open source locally hosted web PDF editor | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
@@ -13,18 +14,23 @@
|
|||||||
|
|
||||||
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
||||||
|
|
||||||
|
Homepage: [https://stirlingpdf.com](https://stirlingpdf.com)
|
||||||
|
|
||||||
|
All documentation available at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
- 50+ PDF Operations
|
||||||
|
- Parallel file processing and downloads
|
||||||
- Dark mode support
|
- Dark mode support
|
||||||
- Custom download options
|
- Custom download options
|
||||||
- Parallel file processing and downloads
|
- Custom 'Pipelines' to run multiple features in a automated queue
|
||||||
- Custom 'Pipelines' to run multiple features in a queue
|
|
||||||
- API for integration with external scripts
|
- API for integration with external scripts
|
||||||
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
|
- Optional Login and Authentication support (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security) for documentation)
|
||||||
- Database Backup and Import (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DATABASE.md) for documentation)
|
- Database Backup and Import (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/DATABASE) for documentation)
|
||||||
|
- Enterprise features like SSO see [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
||||||
|
|
||||||
## PDF Features
|
## PDF Features
|
||||||
|
|
||||||
@@ -90,97 +96,20 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
|||||||
- Get all information on a PDF to view or export as JSON
|
- Get all information on a PDF to view or export as JSON
|
||||||
- Show/detect embedded JavaScript
|
- Show/detect embedded JavaScript
|
||||||
|
|
||||||
For an overview of the tasks and the technology each uses, please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md).
|
|
||||||
|
|
||||||
A demo of the app is available [here](https://stirlingpdf.io).
|
|
||||||
|
|
||||||
## Technologies Used
|
|
||||||
|
|
||||||
- Spring Boot + Thymeleaf
|
# 📖 Get Started
|
||||||
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
|
||||||
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
|
||||||
- [qpdf](https://github.com/qpdf/qpdf)
|
|
||||||
- HTML, CSS, JavaScript
|
|
||||||
- Docker
|
|
||||||
- [PDF.js](https://github.com/mozilla/pdf.js)
|
|
||||||
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
|
||||||
|
|
||||||
## How to Use
|
Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.stirlingpdf.com) for:
|
||||||
|
|
||||||
### Windows
|
- Installation guides for all platforms
|
||||||
|
- Configuration options
|
||||||
|
- Feature documentation
|
||||||
|
- API reference
|
||||||
|
- Security setup
|
||||||
|
- Enterprise features
|
||||||
|
|
||||||
For Windows users, download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe).
|
|
||||||
|
|
||||||
### Locally
|
|
||||||
|
|
||||||
Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md).
|
|
||||||
|
|
||||||
### Docker / Podman
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> <https://hub.docker.com/r/stirlingtools/stirling-pdf>
|
|
||||||
|
|
||||||
Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people who don't mind space optimization, just use the latest tag.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`.
|
|
||||||
|
|
||||||
### Docker Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-v ./trainingData:/usr/share/tessdata \
|
|
||||||
-v ./extraConfigs:/configs \
|
|
||||||
-v ./logs:/logs \
|
|
||||||
# Optional customization (not required)
|
|
||||||
# -v /location/of/customFiles:/customFiles \
|
|
||||||
-e DOCKER_ENABLE_SECURITY=false \
|
|
||||||
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
|
||||||
-e LANGS=en_GB \
|
|
||||||
--name stirling-pdf \
|
|
||||||
stirlingtools/stirling-pdf:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3.3'
|
|
||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
image: stirlingtools/stirling-pdf:latest
|
|
||||||
ports:
|
|
||||||
- '8080:8080'
|
|
||||||
volumes:
|
|
||||||
- ./trainingData:/usr/share/tessdata # Required for extra OCR languages
|
|
||||||
- ./extraConfigs:/configs
|
|
||||||
# - ./customFiles:/customFiles/
|
|
||||||
# - ./logs:/logs/
|
|
||||||
environment:
|
|
||||||
- DOCKER_ENABLE_SECURITY=false
|
|
||||||
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
|
||||||
- LANGS=en_GB
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
|
||||||
|
|
||||||
### Kubernetes
|
|
||||||
|
|
||||||
See the kubernetes helm chart [here](https://github.com/Stirling-Tools/Stirling-PDF-chart)
|
|
||||||
|
|
||||||
## Enable OCR/Compression Feature
|
|
||||||
|
|
||||||
Please view the [HowToUseOCR.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md).
|
|
||||||
|
|
||||||
## Reuse Stored Files
|
|
||||||
|
|
||||||
Certain functionality like `Sign` supports pre-saved files stored at `/customFiles/signatures/`. Image files placed here will be accessible via the web UI. Currently, this supports two folder types:
|
|
||||||
|
|
||||||
- `/customFiles/signatures/ALL_USERS`: Accessible to all users, useful for organizations where many users use the same files or for users not using authentication
|
|
||||||
- `/customFiles/signatures/{username}`: Such as `/customFiles/signatures/froodle`, accessible only to the `froodle` username, private for all others
|
|
||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
@@ -188,236 +117,58 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| 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) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| 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) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| 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) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
|
| 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) |  |
|
||||||
|
|
||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
|
||||||
|
|
||||||
Please see our [Contributing Guide](CONTRIBUTING.md).
|
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Stirling PDF Enterprise
|
||||||
|
|
||||||
Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features and comforts.
|
Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features, support and comforts.
|
||||||
|
Check out our [Enterprise docs](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
||||||
|
|
||||||
### What's included
|
|
||||||
|
|
||||||
- Prioritized Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from the same email domain registered with us)
|
## 🤝 Looking to contribute?
|
||||||
- Prioritised Enhancements to Stirling-PDF where applicable
|
|
||||||
- Base SSO support
|
|
||||||
- Advanced SSO such as automated login handling (Coming very soon)
|
|
||||||
- SAML SSO (Coming very soon)
|
|
||||||
- Custom automated metadata handling
|
|
||||||
- Advanced user configurations (Coming soon)
|
|
||||||
- Plus other exciting features to come
|
|
||||||
|
|
||||||
Check out our [docs](https://docs.stirlingpdf.com/Enterprise%20Edition) on it or our official [website](https://www.stirlingpdf.com)
|
Join our community:
|
||||||
|
- [Contribution Guidelines](CONTRIBUTING.md)
|
||||||
## Customization
|
- [Translation Guide (How to add custom languages)](HowToAddNewLanguage.md)
|
||||||
|
- [Issue Tracker](https://github.com/Stirling-Tools/Stirling-PDF/issues)
|
||||||
Stirling-PDF allows easy customization of the app, including things like:
|
- [Discord Community](https://discord.gg/HYmhKj45pU)
|
||||||
|
- [Developer Guide](DeveloperGuide.md)
|
||||||
- Custom application name
|
|
||||||
- Custom slogans, icons, HTML, images, CSS, etc. (via file overrides)
|
|
||||||
|
|
||||||
There are two options for this, either using the generated settings file `settings.yml`, which is located in the `/configs` directory and follows standard YAML formatting, or using environment variables, which would override the settings file.
|
|
||||||
|
|
||||||
For example, in `settings.yml`, you might have:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
security:
|
|
||||||
enableLogin: 'true'
|
|
||||||
```
|
|
||||||
|
|
||||||
To have this via an environment variable, you would use `SECURITY_ENABLELOGIN`.
|
|
||||||
|
|
||||||
The current list of settings is:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
security:
|
|
||||||
enableLogin: false # set to 'true' to enable login
|
|
||||||
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
|
|
||||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
|
||||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
|
||||||
loginMethod: all # 'all' (Login Username/Password and OAuth2[must be enabled and configured]), 'normal'(only Login with Username/Password) or 'oauth2'(only Login with OAuth2)
|
|
||||||
initialLogin:
|
|
||||||
username: '' # initial username for the first login
|
|
||||||
password: '' # initial password for the first login
|
|
||||||
oauth2:
|
|
||||||
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
|
||||||
client:
|
|
||||||
keycloak:
|
|
||||||
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
|
||||||
clientId: '' # client ID for Keycloak OAuth2
|
|
||||||
clientSecret: '' # client secret for Keycloak OAuth2
|
|
||||||
scopes: openid, profile, email # scopes for Keycloak OAuth2
|
|
||||||
useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2
|
|
||||||
google:
|
|
||||||
clientId: '' # client ID for Google OAuth2
|
|
||||||
clientSecret: '' # client secret for Google OAuth2
|
|
||||||
scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # scopes for Google OAuth2
|
|
||||||
useAsUsername: email # field to use as the username for Google OAuth2
|
|
||||||
github:
|
|
||||||
clientId: '' # client ID for GitHub OAuth2
|
|
||||||
clientSecret: '' # client secret for GitHub OAuth2
|
|
||||||
scopes: read:user # scope for GitHub OAuth2
|
|
||||||
useAsUsername: login # field to use as the username for GitHub OAuth2
|
|
||||||
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
|
||||||
clientId: '' # client ID from your provider
|
|
||||||
clientSecret: '' # client secret from your provider
|
|
||||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
|
||||||
useAsUsername: email # default is 'email'; custom fields can be used as the username
|
|
||||||
scopes: openid, profile, email # specify the scopes for which the application will request permissions
|
|
||||||
provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
|
||||||
saml2:
|
|
||||||
enabled: false # currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true
|
|
||||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
|
||||||
registrationId: stirling
|
|
||||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
|
||||||
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
|
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
|
||||||
idpIssuer: http://www.okta.com/externalKey
|
|
||||||
idpCert: classpath:okta.crt
|
|
||||||
privateKey: classpath:saml-private-key.key
|
|
||||||
spCert: classpath:saml-public-cert.crt
|
|
||||||
|
|
||||||
enterpriseEdition:
|
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
|
||||||
key: 00000000-0000-0000-0000-000000000000
|
|
||||||
CustomMetadata:
|
|
||||||
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
|
||||||
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
|
|
||||||
creator: Stirling-PDF # supports text such as 'Company-PDF'
|
|
||||||
producer: Stirling-PDF # supports text such as 'Company-PDF'
|
|
||||||
|
|
||||||
legal:
|
|
||||||
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
|
||||||
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
|
|
||||||
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
|
|
||||||
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
|
|
||||||
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum). Empty string to disable or filename to load from local file in static folder
|
|
||||||
|
|
||||||
system:
|
|
||||||
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
|
||||||
enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
|
|
||||||
showUpdate: false # see when a new update is available
|
|
||||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
|
||||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
|
||||||
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
|
||||||
enableAnalytics: undefined # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
|
||||||
|
|
||||||
ui:
|
|
||||||
appName: '' # application's visible name
|
|
||||||
homeDescription: '' # short description or tagline shown on the homepage
|
|
||||||
appNameNavbar: '' # name displayed on the navigation bar
|
|
||||||
|
|
||||||
endpoints:
|
|
||||||
toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
|
||||||
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
|
|
||||||
|
|
||||||
metrics:
|
|
||||||
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
|
|
||||||
|
|
||||||
# Automatically Generated Settings (Do Not Edit Directly)
|
|
||||||
AutomaticallyGenerated:
|
|
||||||
key: example
|
|
||||||
UUID: example
|
|
||||||
```
|
|
||||||
|
|
||||||
There is an additional config file `/configs/custom_settings.yml` where users familiar with Java and Spring `application.properties` can input their own settings on top of Stirling-PDF's existing ones.
|
|
||||||
|
|
||||||
### Extra Notes
|
|
||||||
|
|
||||||
- **Endpoints**: Currently, the `ENDPOINTS_TO_REMOVE` and `GROUPS_TO_REMOVE` endpoints can include comma-separated lists of endpoints and groups to disable. For example, `ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages` would disable both image-to-pdf and remove pages, while `GROUPS_TO_REMOVE=LibreOffice` would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md).
|
|
||||||
- **customStaticFilePath**: Customize static files such as the app logo by placing files in the `/customFiles/static/` directory. An example of customizing the app logo is placing `/customFiles/static/favicon.svg` to override the current SVG. This can be used to change any `images/icons/css/fonts/js`, etc. in Stirling-PDF.
|
|
||||||
|
|
||||||
### Environment-Only Parameters
|
|
||||||
|
|
||||||
- `SYSTEM_ROOTURIPATH` - Set the application's root URI (e.g. `/pdf-app` to set the root URI to `localhost:8080/pdf-app`)
|
|
||||||
- `SYSTEM_CONNECTIONTIMEOUTMINUTES` - Set custom connection timeout values
|
|
||||||
- `DOCKER_ENABLE_SECURITY` - Set to `true` to download security jar (required for authentication login)
|
|
||||||
- `INSTALL_BOOK_AND_ADVANCED_HTML_OPS` - Download Calibre onto Stirling-PDF to enable PDF to/from book and advanced HTML conversion
|
|
||||||
- `LANGS` - Define custom font libraries to install for document conversions
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
For those wanting to use Stirling-PDF's backend API to link with their own custom scripting to edit PDFs, you can view all existing API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/), or navigate to `/swagger-ui/index.html` of your Stirling-PDF instance for your version's documentation (or by following the API button in the settings of Stirling-PDF).
|
|
||||||
|
|
||||||
## Login Authentication
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- User must have the folder `./configs` volumed within Docker so that it is retained during updates.
|
|
||||||
- Docker users must download the security jar version by setting `DOCKER_ENABLE_SECURITY` to `true` in environment variables.
|
|
||||||
- Then either enable login via the `settings.yml` file or set `SECURITY_ENABLE_LOGIN` to `true`.
|
|
||||||
- Now the initial user will be generated with username `admin` and password `stirling`. On login, you will be forced to change the password to a new one. You can also use the environment variables `SECURITY_INITIALLOGIN_USERNAME` and `SECURITY_INITIALLOGIN_PASSWORD` to set your own credentials straight away (recommended to remove them after user creation).
|
|
||||||
|
|
||||||
Once the above has been done, on restart, a new `stirling-pdf-DB.mv.db` will show if everything worked.
|
|
||||||
|
|
||||||
When you log in to Stirling-PDF, you will be redirected to the `/login` page to log in with those default credentials. After login, everything should function as normal.
|
|
||||||
|
|
||||||
To access your account settings, go to Account Settings in the settings cog menu (top right in the navbar). This Account Settings menu is also where you find your API key.
|
|
||||||
|
|
||||||
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here, you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
|
||||||
|
|
||||||
For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user.
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### Q1: What are your planned features?
|
|
||||||
|
|
||||||
- Progress bar/tracking
|
|
||||||
- Full custom logic pipelines to combine multiple operations together
|
|
||||||
- Folder support with auto-scanning to perform operations on
|
|
||||||
- Redact text (via UI, not just automated)
|
|
||||||
- Add forms
|
|
||||||
- Multi-page layout (stitch PDF pages together) support x rows y columns and custom page sizing
|
|
||||||
- Fill forms manually or automatically
|
|
||||||
|
|
||||||
### Q2: Why is my application downloading .htm files? Why am I getting HTTP error 413?
|
|
||||||
|
|
||||||
This is an issue commonly caused by your NGINX configuration. The default file upload size for NGINX is 1MB. You need to add the following in your Nginx sites-available file: `client_max_body_size SIZE;` (where "SIZE" is 50M, for example, for 50MB files).
|
|
||||||
|
|
||||||
### Q3: Why is my download timing out?
|
|
||||||
|
|
||||||
NGINX has timeout values by default, so if you are running Stirling-PDF behind NGINX, you may need to set a timeout value, such as adding the config `proxy_read_timeout 3600;`.
|
|
||||||
|
|||||||
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:
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
All versions in a Docker environment can download Calibre as a optional extra at runtime to support `book-to-pdf` and `pdf-to-book` using parameter ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS``.
|
|
||||||
The 'Fat' container contains all those found in 'Full' with security jar along with this Calibre install.
|
|
||||||
|
|
||||||
| Technology | Ultra-Lite | Full |
|
|
||||||
| ---------- | :--------: | :---: |
|
|
||||||
| Java | ✔️ | ✔️ |
|
|
||||||
| JavaScript | ✔️ | ✔️ |
|
|
||||||
| Libre | | ✔️ |
|
|
||||||
| Python | | ✔️ |
|
|
||||||
| OpenCV | | ✔️ |
|
|
||||||
| qpdf | | ✔️ |
|
|
||||||
|
|
||||||
| Operation | Ultra-Lite | Full |
|
|
||||||
| ---------------------- | ---------- | ---- |
|
|
||||||
| add-page-numbers | ✔️ | ✔️ |
|
|
||||||
| add-password | ✔️ | ✔️ |
|
|
||||||
| add-image | ✔️ | ✔️ |
|
|
||||||
| add-watermark | ✔️ | ✔️ |
|
|
||||||
| adjust-contrast | ✔️ | ✔️ |
|
|
||||||
| auto-split-pdf | ✔️ | ✔️ |
|
|
||||||
| auto-redact | ✔️ | ✔️ |
|
|
||||||
| auto-rename | ✔️ | ✔️ |
|
|
||||||
| cert-sign | ✔️ | ✔️ |
|
|
||||||
| remove-cert-sign | ✔️ | ✔️ |
|
|
||||||
| crop | ✔️ | ✔️ |
|
|
||||||
| change-metadata | ✔️ | ✔️ |
|
|
||||||
| change-permissions | ✔️ | ✔️ |
|
|
||||||
| compare | ✔️ | ✔️ |
|
|
||||||
| extract-page | ✔️ | ✔️ |
|
|
||||||
| extract-images | ✔️ | ✔️ |
|
|
||||||
| flatten | ✔️ | ✔️ |
|
|
||||||
| get-info-on-pdf | ✔️ | ✔️ |
|
|
||||||
| img-to-pdf | ✔️ | ✔️ |
|
|
||||||
| markdown-to-pdf | ✔️ | ✔️ |
|
|
||||||
| merge-pdfs | ✔️ | ✔️ |
|
|
||||||
| multi-page-layout | ✔️ | ✔️ |
|
|
||||||
| overlay-pdf | ✔️ | ✔️ |
|
|
||||||
| pdf-organizer | ✔️ | ✔️ |
|
|
||||||
| pdf-to-csv | ✔️ | ✔️ |
|
|
||||||
| pdf-to-img | ✔️ | ✔️ |
|
|
||||||
| pdf-to-single-page | ✔️ | ✔️ |
|
|
||||||
| remove-pages | ✔️ | ✔️ |
|
|
||||||
| remove-password | ✔️ | ✔️ |
|
|
||||||
| rotate-pdf | ✔️ | ✔️ |
|
|
||||||
| sanitize-pdf | ✔️ | ✔️ |
|
|
||||||
| scale-pages | ✔️ | ✔️ |
|
|
||||||
| sign | ✔️ | ✔️ |
|
|
||||||
| show-javascript | ✔️ | ✔️ |
|
|
||||||
| split-by-size-or-count | ✔️ | ✔️ |
|
|
||||||
| split-pdf-by-sections | ✔️ | ✔️ |
|
|
||||||
| split-pdfs | ✔️ | ✔️ |
|
|
||||||
| compress-pdf | | ✔️ |
|
|
||||||
| extract-image-scans | | ✔️ |
|
|
||||||
| ocr-pdf | | ✔️ |
|
|
||||||
| pdf-to-pdfa | | ✔️ |
|
|
||||||
| remove-blanks | | ✔️ |
|
|
||||||
| pdf-to-text | ✔️ | ✔️ |
|
|
||||||
| pdf-to-html | | ✔️ |
|
|
||||||
| pdf-to-word | | ✔️ |
|
|
||||||
| pdf-to-presentation | | ✔️ |
|
|
||||||
| pdf-to-xml | | ✔️ |
|
|
||||||
| remove-annotations | ✔️ | ✔️ |
|
|
||||||
| remove-cert-sign | ✔️ | ✔️ |
|
|
||||||
| remove-image-pdf | ✔️ | ✔️ |
|
|
||||||
| file-to-pdf | | ✔️ |
|
|
||||||
| html-to-pdf | | ✔️ |
|
|
||||||
| url-to-pdf | | ✔️ |
|
|
||||||
| repair | | ✔️ |
|
|
||||||
65
build.gradle
65
build.gradle
@@ -5,14 +5,12 @@ 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@@ -27,7 +25,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.36.5"
|
version = "0.37.1"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -52,13 +50,15 @@ sourceSets {
|
|||||||
java {
|
java {
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
||||||
exclude "stirling/software/SPDF/config/security/**"
|
exclude "stirling/software/SPDF/config/security/**"
|
||||||
exclude "stirling/software/SPDF/controller/api/UserController.java"
|
|
||||||
exclude "stirling/software/SPDF/controller/api/DatabaseController.java"
|
exclude "stirling/software/SPDF/controller/api/DatabaseController.java"
|
||||||
|
exclude "stirling/software/SPDF/controller/api/UserController.java"
|
||||||
|
exclude "stirling/software/SPDF/controller/api/H2SQLCondition.java"
|
||||||
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
|
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
|
||||||
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
|
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
|
||||||
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
|
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
|
||||||
exclude "stirling/software/SPDF/model/AttemptCounter.java"
|
exclude "stirling/software/SPDF/model/AttemptCounter.java"
|
||||||
exclude "stirling/software/SPDF/model/Authority.java"
|
exclude "stirling/software/SPDF/model/Authority.java"
|
||||||
|
exclude "stirling/software/SPDF/model/BackupNotFoundException.java"
|
||||||
exclude "stirling/software/SPDF/model/PersistentLogin.java"
|
exclude "stirling/software/SPDF/model/PersistentLogin.java"
|
||||||
exclude "stirling/software/SPDF/model/SessionEntity.java"
|
exclude "stirling/software/SPDF/model/SessionEntity.java"
|
||||||
exclude "stirling/software/SPDF/model/User.java"
|
exclude "stirling/software/SPDF/model/User.java"
|
||||||
@@ -69,7 +69,29 @@ sourceSets {
|
|||||||
exclude "stirling/software/SPDF/UI/impl/**"
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
||||||
|
exclude "stirling/software/SPDF/config/security/**"
|
||||||
|
exclude "stirling/software/SPDF/controller/api/UserControllerTest.java"
|
||||||
|
exclude "stirling/software/SPDF/controller/api/DatabaseControllerTest.java"
|
||||||
|
exclude "stirling/software/SPDF/controller/web/AccountWebControllerTest.java"
|
||||||
|
exclude "stirling/software/SPDF/controller/web/DatabaseWebControllerTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/AttemptCounterTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/AuthorityTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/PersistentLoginTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/SessionEntityTest.java"
|
||||||
|
exclude "stirling/software/SPDF/model/UserTest.java"
|
||||||
|
exclude "stirling/software/SPDF/repository/**"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||||
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,10 +130,13 @@ jpackage {
|
|||||||
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
||||||
"-Djava.awt.headless=false",
|
"-Djava.awt.headless=false",
|
||||||
"-Dapple.awt.UIElement=true",
|
"-Dapple.awt.UIElement=true",
|
||||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||||
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
|
"--add-opens=java.desktop/java.awt.event=ALL-UNNAMED",
|
||||||
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
|
"--add-opens=java.desktop/sun.awt=ALL-UNNAMED",
|
||||||
|
"--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED",
|
||||||
|
"--add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED",
|
||||||
|
"--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED",
|
||||||
|
"--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -123,11 +148,13 @@ jpackage {
|
|||||||
windows {
|
windows {
|
||||||
launcherAsService = false
|
launcherAsService = false
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
winConsole = false
|
|
||||||
winDirChooser = true
|
winConsole = false
|
||||||
winMenu = true
|
winMenu = true // Creates start menu entry
|
||||||
winShortcut = true
|
winShortcut = true // Creates desktop shortcut
|
||||||
winPerUserInstall = true
|
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
||||||
|
winDirChooser = true // Allows users to choose installation directory
|
||||||
|
winPerUserInstall = false
|
||||||
winMenuGroup = "Stirling Software"
|
winMenuGroup = "Stirling Software"
|
||||||
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
|
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
|
||||||
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
|
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
|
||||||
@@ -257,7 +284,7 @@ spotless {
|
|||||||
// rules=['unused-dependency']
|
// rules=['unused-dependency']
|
||||||
// }
|
// }
|
||||||
tasks.wrapper {
|
tasks.wrapper {
|
||||||
gradleVersion = "8.7"
|
gradleVersion = "8.12"
|
||||||
}
|
}
|
||||||
//tasks.withType(JavaCompile) {
|
//tasks.withType(JavaCompile) {
|
||||||
// options.compilerArgs << "-Xlint:deprecation"
|
// options.compilerArgs << "-Xlint:deprecation"
|
||||||
@@ -297,10 +324,12 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||||
|
implementation "org.springframework:spring-jdbc:6.2.1"
|
||||||
|
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
// Don't upgrade h2database
|
// Don't upgrade h2database
|
||||||
runtimeOnly "com.h2database:h2:2.3.232"
|
runtimeOnly "com.h2database:h2:2.3.232"
|
||||||
|
runtimeOnly "org.postgresql:postgresql:42.7.4"
|
||||||
constraints {
|
constraints {
|
||||||
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
||||||
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
||||||
@@ -344,7 +373,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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +397,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"
|
||||||
@@ -377,6 +406,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,7 @@
|
|||||||
@general
|
@general
|
||||||
Feature: API Validation
|
Feature: API Validation
|
||||||
|
|
||||||
|
|
||||||
@split-pdf-by-sections @positive
|
@split-pdf-by-sections @positive
|
||||||
Scenario Outline: split-pdf-by-sections with different parameters
|
Scenario Outline: split-pdf-by-sections with different parameters
|
||||||
Given I generate a PDF file as "fileInput"
|
Given I generate a PDF file as "fileInput"
|
||||||
@@ -66,7 +66,7 @@ Feature: API Validation
|
|||||||
| pageNumbers | file_count |
|
| pageNumbers | file_count |
|
||||||
| 1,3,5-9 | 8 |
|
| 1,3,5-9 | 8 |
|
||||||
| all | 20 |
|
| all | 20 |
|
||||||
| 2n+1 | 11 |
|
| 2n+1 | 10 |
|
||||||
| 3n | 7 |
|
| 3n | 7 |
|
||||||
|
|
||||||
|
|
||||||
@@ -106,9 +106,9 @@ Feature: API Validation
|
|||||||
And the response ZIP should contain 2 files
|
And the response ZIP should contain 2 files
|
||||||
And the response file should have size greater than 0
|
And the response file should have size greater than 0
|
||||||
And the response status code should be 200
|
And the response status code should be 200
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| format |
|
| format |
|
||||||
| png |
|
| png |
|
||||||
| gif |
|
| gif |
|
||||||
| jpeg |
|
| jpeg |
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
5
cucumber/requirements.in
Normal file
5
cucumber/requirements.in
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
behave
|
||||||
|
requests
|
||||||
|
pypdf
|
||||||
|
reportlab
|
||||||
|
PyCryptodome
|
||||||
@@ -1,5 +1,259 @@
|
|||||||
behave
|
#
|
||||||
requests
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
PyPDF2
|
# by the following command:
|
||||||
reportlab
|
#
|
||||||
PyCryptodome
|
# pip-compile --generate-hashes --output-file='cucumber\requirements.txt' 'cucumber\requirements.in'
|
||||||
|
#
|
||||||
|
behave==1.2.6 \
|
||||||
|
--hash=sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86 \
|
||||||
|
--hash=sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c
|
||||||
|
# via -r cucumber\requirements.in
|
||||||
|
certifi==2024.12.14 \
|
||||||
|
--hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \
|
||||||
|
--hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db
|
||||||
|
# via requests
|
||||||
|
chardet==5.2.0 \
|
||||||
|
--hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \
|
||||||
|
--hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970
|
||||||
|
# via reportlab
|
||||||
|
charset-normalizer==3.4.1 \
|
||||||
|
--hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \
|
||||||
|
--hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \
|
||||||
|
--hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \
|
||||||
|
--hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \
|
||||||
|
--hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \
|
||||||
|
--hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \
|
||||||
|
--hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \
|
||||||
|
--hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \
|
||||||
|
--hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \
|
||||||
|
--hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \
|
||||||
|
--hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \
|
||||||
|
--hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \
|
||||||
|
--hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \
|
||||||
|
--hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \
|
||||||
|
--hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \
|
||||||
|
--hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \
|
||||||
|
--hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \
|
||||||
|
--hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \
|
||||||
|
--hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \
|
||||||
|
--hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \
|
||||||
|
--hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \
|
||||||
|
--hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \
|
||||||
|
--hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \
|
||||||
|
--hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \
|
||||||
|
--hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \
|
||||||
|
--hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \
|
||||||
|
--hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \
|
||||||
|
--hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \
|
||||||
|
--hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \
|
||||||
|
--hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \
|
||||||
|
--hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \
|
||||||
|
--hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \
|
||||||
|
--hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \
|
||||||
|
--hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \
|
||||||
|
--hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \
|
||||||
|
--hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \
|
||||||
|
--hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \
|
||||||
|
--hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \
|
||||||
|
--hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \
|
||||||
|
--hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \
|
||||||
|
--hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \
|
||||||
|
--hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \
|
||||||
|
--hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \
|
||||||
|
--hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \
|
||||||
|
--hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \
|
||||||
|
--hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \
|
||||||
|
--hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \
|
||||||
|
--hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \
|
||||||
|
--hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \
|
||||||
|
--hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \
|
||||||
|
--hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \
|
||||||
|
--hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \
|
||||||
|
--hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \
|
||||||
|
--hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \
|
||||||
|
--hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \
|
||||||
|
--hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \
|
||||||
|
--hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \
|
||||||
|
--hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \
|
||||||
|
--hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \
|
||||||
|
--hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \
|
||||||
|
--hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \
|
||||||
|
--hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \
|
||||||
|
--hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \
|
||||||
|
--hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \
|
||||||
|
--hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \
|
||||||
|
--hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \
|
||||||
|
--hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \
|
||||||
|
--hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \
|
||||||
|
--hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \
|
||||||
|
--hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \
|
||||||
|
--hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \
|
||||||
|
--hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \
|
||||||
|
--hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \
|
||||||
|
--hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \
|
||||||
|
--hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \
|
||||||
|
--hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \
|
||||||
|
--hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \
|
||||||
|
--hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \
|
||||||
|
--hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \
|
||||||
|
--hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \
|
||||||
|
--hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \
|
||||||
|
--hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \
|
||||||
|
--hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \
|
||||||
|
--hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \
|
||||||
|
--hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \
|
||||||
|
--hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \
|
||||||
|
--hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \
|
||||||
|
--hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \
|
||||||
|
--hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \
|
||||||
|
--hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \
|
||||||
|
--hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \
|
||||||
|
--hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616
|
||||||
|
# via requests
|
||||||
|
idna==3.10 \
|
||||||
|
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
|
||||||
|
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
|
||||||
|
# via requests
|
||||||
|
parse==1.20.2 \
|
||||||
|
--hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \
|
||||||
|
--hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce
|
||||||
|
# via
|
||||||
|
# behave
|
||||||
|
# parse-type
|
||||||
|
parse-type==0.6.4 \
|
||||||
|
--hash=sha256:5e1ec10440b000c3f818006033372939e693a9ec0176f446d9303e4db88489a6 \
|
||||||
|
--hash=sha256:83d41144a82d6b8541127bf212dd76c7f01baff680b498ce8a4d052a7a5bce4c
|
||||||
|
# via behave
|
||||||
|
pillow==11.1.0 \
|
||||||
|
--hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \
|
||||||
|
--hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \
|
||||||
|
--hash=sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65 \
|
||||||
|
--hash=sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a \
|
||||||
|
--hash=sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352 \
|
||||||
|
--hash=sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f \
|
||||||
|
--hash=sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20 \
|
||||||
|
--hash=sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c \
|
||||||
|
--hash=sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114 \
|
||||||
|
--hash=sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49 \
|
||||||
|
--hash=sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91 \
|
||||||
|
--hash=sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0 \
|
||||||
|
--hash=sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2 \
|
||||||
|
--hash=sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5 \
|
||||||
|
--hash=sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884 \
|
||||||
|
--hash=sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e \
|
||||||
|
--hash=sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c \
|
||||||
|
--hash=sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196 \
|
||||||
|
--hash=sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756 \
|
||||||
|
--hash=sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861 \
|
||||||
|
--hash=sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269 \
|
||||||
|
--hash=sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1 \
|
||||||
|
--hash=sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb \
|
||||||
|
--hash=sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a \
|
||||||
|
--hash=sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081 \
|
||||||
|
--hash=sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1 \
|
||||||
|
--hash=sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8 \
|
||||||
|
--hash=sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90 \
|
||||||
|
--hash=sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc \
|
||||||
|
--hash=sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5 \
|
||||||
|
--hash=sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1 \
|
||||||
|
--hash=sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3 \
|
||||||
|
--hash=sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35 \
|
||||||
|
--hash=sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f \
|
||||||
|
--hash=sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c \
|
||||||
|
--hash=sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2 \
|
||||||
|
--hash=sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2 \
|
||||||
|
--hash=sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf \
|
||||||
|
--hash=sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65 \
|
||||||
|
--hash=sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b \
|
||||||
|
--hash=sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442 \
|
||||||
|
--hash=sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2 \
|
||||||
|
--hash=sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade \
|
||||||
|
--hash=sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482 \
|
||||||
|
--hash=sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe \
|
||||||
|
--hash=sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc \
|
||||||
|
--hash=sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a \
|
||||||
|
--hash=sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec \
|
||||||
|
--hash=sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3 \
|
||||||
|
--hash=sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a \
|
||||||
|
--hash=sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07 \
|
||||||
|
--hash=sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6 \
|
||||||
|
--hash=sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f \
|
||||||
|
--hash=sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e \
|
||||||
|
--hash=sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192 \
|
||||||
|
--hash=sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0 \
|
||||||
|
--hash=sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6 \
|
||||||
|
--hash=sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73 \
|
||||||
|
--hash=sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f \
|
||||||
|
--hash=sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6 \
|
||||||
|
--hash=sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547 \
|
||||||
|
--hash=sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9 \
|
||||||
|
--hash=sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457 \
|
||||||
|
--hash=sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8 \
|
||||||
|
--hash=sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26 \
|
||||||
|
--hash=sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5 \
|
||||||
|
--hash=sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab \
|
||||||
|
--hash=sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070 \
|
||||||
|
--hash=sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71 \
|
||||||
|
--hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \
|
||||||
|
--hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761
|
||||||
|
# via reportlab
|
||||||
|
pycryptodome==3.21.0 \
|
||||||
|
--hash=sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8 \
|
||||||
|
--hash=sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d \
|
||||||
|
--hash=sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0 \
|
||||||
|
--hash=sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93 \
|
||||||
|
--hash=sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4 \
|
||||||
|
--hash=sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a \
|
||||||
|
--hash=sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764 \
|
||||||
|
--hash=sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca \
|
||||||
|
--hash=sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e \
|
||||||
|
--hash=sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e \
|
||||||
|
--hash=sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd \
|
||||||
|
--hash=sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f \
|
||||||
|
--hash=sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6 \
|
||||||
|
--hash=sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb \
|
||||||
|
--hash=sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e \
|
||||||
|
--hash=sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1 \
|
||||||
|
--hash=sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6 \
|
||||||
|
--hash=sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a \
|
||||||
|
--hash=sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c \
|
||||||
|
--hash=sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2 \
|
||||||
|
--hash=sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4 \
|
||||||
|
--hash=sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3 \
|
||||||
|
--hash=sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819 \
|
||||||
|
--hash=sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568 \
|
||||||
|
--hash=sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53 \
|
||||||
|
--hash=sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3 \
|
||||||
|
--hash=sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8 \
|
||||||
|
--hash=sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd \
|
||||||
|
--hash=sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b \
|
||||||
|
--hash=sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b \
|
||||||
|
--hash=sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297 \
|
||||||
|
--hash=sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58
|
||||||
|
# via -r cucumber\requirements.in
|
||||||
|
pypdf==5.1.0 \
|
||||||
|
--hash=sha256:3bd4f503f4ebc58bae40d81e81a9176c400cbbac2ba2d877367595fb524dfdfc \
|
||||||
|
--hash=sha256:425a129abb1614183fd1aca6982f650b47f8026867c0ce7c4b9f281c443d2740
|
||||||
|
# via -r cucumber\requirements.in
|
||||||
|
reportlab==4.2.5 \
|
||||||
|
--hash=sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f \
|
||||||
|
--hash=sha256:eb2745525a982d9880babb991619e97ac3f661fae30571b7d50387026ca765ee
|
||||||
|
# via -r cucumber\requirements.in
|
||||||
|
requests==2.32.3 \
|
||||||
|
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||||
|
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
||||||
|
# via -r cucumber\requirements.in
|
||||||
|
six==1.17.0 \
|
||||||
|
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
||||||
|
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||||
|
# via
|
||||||
|
# behave
|
||||||
|
# parse-type
|
||||||
|
typing-extensions==4.12.2 \
|
||||||
|
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||||
|
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||||
|
# via pypdf
|
||||||
|
urllib3==2.3.0 \
|
||||||
|
--hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
|
||||||
|
--hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
|
||||||
|
# via requests
|
||||||
|
|||||||
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
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security-Fat-Postgres
|
||||||
|
image: stirlingtools/stirling-pdf:latest-fat-postgres
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'" ]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- ./stirling/latest/config:/configs:rw
|
||||||
|
- ./stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
PUID: 1002
|
||||||
|
PGID: 1002
|
||||||
|
UMASK: "022"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security and PostgreSQL
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat-PostgreSQL
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "true"
|
||||||
|
SYSTEM_DATASOURCE_CUSTOMDATABASEURL: "jdbc:postgresql://db:5432/stirling_pdf"
|
||||||
|
SYSTEM_DATASOURCE_USERNAME: "admin"
|
||||||
|
SYSTEM_DATASOURCE_PASSWORD: "stirling"
|
||||||
|
restart: on-failure:5
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: 'postgres:17.2-alpine'
|
||||||
|
restart: on-failure:5
|
||||||
|
container_name: db
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "stirling_pdf"
|
||||||
|
POSTGRES_USER: "admin"
|
||||||
|
POSTGRES_PASSWORD: "stirling"
|
||||||
|
shm_size: "512mb"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512m
|
||||||
|
cpus: "0.5"
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "pg_isready -U admin stirling_pdf" ]
|
||||||
|
interval: 1s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
volumes:
|
||||||
|
- ./stirling/latest/data:/pgdata
|
||||||
@@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "false"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
|||||||
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
|
||||||
6568
gradle/verification-keyring.keys
Normal file
6568
gradle/verification-keyring.keys
Normal file
File diff suppressed because it is too large
Load Diff
477
gradle/verification-metadata.xml
Normal file
477
gradle/verification-metadata.xml
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
|
||||||
|
<configuration>
|
||||||
|
<verify-metadata>true</verify-metadata>
|
||||||
|
<verify-signatures>true</verify-signatures>
|
||||||
|
<keyring-format>armored</keyring-format>
|
||||||
|
<trusted-artifacts>
|
||||||
|
<trust group="io.dropwizard.metrics" name="metrics-bom" reason="BOM file, safe to trust"/>
|
||||||
|
<trust group="io.dropwizard.metrics" name="metrics-parent" reason="BOM parent, https://github.com/gradle/gradle/issues/20194"/>
|
||||||
|
<trust group="org.springframework" name="spring-framework-bom" reason="Spring BOM file, safe to trust"/>
|
||||||
|
</trusted-artifacts>
|
||||||
|
<trusted-keys>
|
||||||
|
<trusted-key id="015479E1055341431B4545AB72475FD306B9CAB7" group="com.googlecode.javaewah" name="JavaEWAH" version="1.2.3"/>
|
||||||
|
<trusted-key id="042B29E928995B9DB963C636C7CA19B7B620D787" group="com.github.stephenc.jcip" name="jcip-annotations" version="1.0-1"/>
|
||||||
|
<trusted-key id="04543577D6A9CC626239C50C7ECBD740FF06AEB5">
|
||||||
|
<trusting group="org.glassfish.jaxb"/>
|
||||||
|
<trusting group="^com[.]sun($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="050A37A2E0577F4BAA095B52602EC18D20C4661C">
|
||||||
|
<trusting group="com.thoughtworks.xstream"/>
|
||||||
|
<trusting group="io.github.x-stream"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="077E8893A6DCC33DD4A4D5B256E73BA9A0B592D0" group="^org[.]apache[.]logging($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="0785B3EFF60B1B1BEA94E0BB7C25280EAE63EBE5" group="^org[.]apache[.]httpcomponents($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="07E20F0103D9DFC697C490D0368557390486F2C5">
|
||||||
|
<trusting group="io.rest-assured"/>
|
||||||
|
<trusting group="org.awaitility"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="08F0AAB4D0C1A4BDDE340765B341DDB020FCB6AB" group="org.bouncycastle"/>
|
||||||
|
<trusted-key id="0A60B3F1FCB211175300EC206E50BB68CC1699A6" group="com.github.jai-imageio"/>
|
||||||
|
<trusted-key id="0B1B71E813C226033B16D8C5F0D228D8FF31B515" group="^io[.]zipkin($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="0B743A794876D3C78AB542A118D239B1CBCD2236" group="org.glassfish.jersey" name="jersey-bom"/>
|
||||||
|
<trusted-key id="0CC641C3A62453AB390066C4A41F13C999945293" group="commons-collections" name="commons-collections" version="3.2.2"/>
|
||||||
|
<trusted-key id="0CDE80149711EB46DFF17AE421A24B3F8B0F594A" group="org.apache" name="apache" version="16"/>
|
||||||
|
<trusted-key id="0CFA413799E2464C7D7E26220A4B343F2A55FDAE" group="com.h2database" name="h2" version="2.3.232"/>
|
||||||
|
<trusted-key id="0D35D3F60078655126908E8AF3D1600878E85A3D" group="io.netty" name="netty-bom"/>
|
||||||
|
<trusted-key id="0E0CA56D354132B5E646C25F49A1796B9B494CB8" group="org.opensaml"/>
|
||||||
|
<trusted-key id="0E9BD9062B021BBA50F41EEB9549F6CB1E679A56" group="org.locationtech.jts"/>
|
||||||
|
<trusted-key id="10F3C7A02ECA55E502BADCF3991EFB94DB91127D" group="org.ow2" name="ow2" version="1.5.1"/>
|
||||||
|
<trusted-key id="1452F35849B50750F6A3BBB4B54011358B352F85" group="org.hibernate.orm" name="hibernate-core" version="6.6.4.Final"/>
|
||||||
|
<trusted-key id="147B691A19097624902F4EA9689CBE64F4BC997F" group="org.mockito"/>
|
||||||
|
<trusted-key id="190D5A957FF22273E601F7A7C92C5FEC70161C62" group="org.apache" name="apache" version="18"/>
|
||||||
|
<trusted-key id="19BEAB2D799C020F17C69126B16698A4ADF4D638" group="org.checkerframework" name="checker-qual"/>
|
||||||
|
<trusted-key id="1AA8CF92D409A73393D0B736BFF2EE42C8282E76" group="org.apache.activemq" name="activemq-bom" version="6.1.4"/>
|
||||||
|
<trusted-key id="1D04A424F505394DBED15D451D0690E353BE126D" group="net.minidev"/>
|
||||||
|
<trusted-key id="1D2C7EF8ADA0F794B58C7C63436902AF59EDF60E">
|
||||||
|
<trusting group="dev.equo.ide" name="solstice" version="1.7.5"/>
|
||||||
|
<trusting group="dev.equo.ide" name="solstice" version="1.8.0"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="20FC6EC5F628F0EB66F157B8DC97B815CAC4E847" group="io.github.pixee" name="java-security-toolkit" version="1.2.1"/>
|
||||||
|
<trusted-key id="2518174F4111F02779592A6F9757D7E7E06DD2AC" group="io.prometheus"/>
|
||||||
|
<trusted-key id="2655176F748FD83725B4805FF2A01147D830C125" group="org.testcontainers" name="testcontainers-bom"/>
|
||||||
|
<trusted-key id="28118C070CB22A0175A2E8D43D12CA2AC19F3181" group="^com[.]fasterxml($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="28417C95E8906D108392822354A43F3254868410" group="org.apache.activemq"/>
|
||||||
|
<trusted-key id="2B1DD4CE9223D4E19C73531E5657B51F13E59DBE" group="com.unboundid.product.scim2"/>
|
||||||
|
<trusted-key id="2B34821418CF19CF1F2A8352953E02E4F573B46F" group="jakarta.platform"/>
|
||||||
|
<trusted-key id="2BCBDD0F23EA1CAFCC11D4860374CF2E8DD1BDFD" group="net.java"/>
|
||||||
|
<trusted-key id="2DB4F1EF0FA761ECC4EA935C86FDC7E2A11262CB">
|
||||||
|
<trusting group="commons-beanutils" name="commons-beanutils" version="1.10.0"/>
|
||||||
|
<trusting group="commons-codec"/>
|
||||||
|
<trusting group="commons-io"/>
|
||||||
|
<trusting group="commons-logging"/>
|
||||||
|
<trusting group="org.apache.commons"/>
|
||||||
|
<trusting group="xml-apis"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="2DC48CBB4352B4953AF6F803D433B437192A0FD1" group="com.datastax.oss" name="java-driver-bom" version="4.15.0"/>
|
||||||
|
<trusted-key id="2E3A1AFFE42B5F53AF19F780BCF4173966770193" group="org.jetbrains" name="annotations" version="13.0"/>
|
||||||
|
<trusted-key id="2FC53E6B1F681184F4CCD637F5C81DE10A0B8ECC" group="org.yaml" name="snakeyaml" version="2.3"/>
|
||||||
|
<trusted-key id="3262A061C42FC4C7BBB5C25C1CF0293FA53CA458" group="org.apache.tomcat.embed"/>
|
||||||
|
<trusted-key id="34441E504A937F43EB0DAEF96A65176A0FB1CD0B" group="org.apache.groovy" name="groovy-bom"/>
|
||||||
|
<trusted-key id="3690C240CE51B4670D30AD1C38EE757D69184620" group="org.tukaani" name="xz" version="1.9"/>
|
||||||
|
<trusted-key id="3750777B9C4B7D233B9D0C40307A96FBA0292109" group="org.postgresql" name="postgresql" version="42.7.4"/>
|
||||||
|
<trusted-key id="38319E05F62674572CDF886170B2EBE96C112CC9" group="org.cryptacular" name="cryptacular" version="1.2.5"/>
|
||||||
|
<trusted-key id="3E61D8C230332482009D7F0EDB901B24CAD38BC4" group="io.swagger.core.v3"/>
|
||||||
|
<trusted-key id="3F05DDA9F317301E927136D417A27CE7A60FF5F0" group="io.opentelemetry" name="opentelemetry-bom"/>
|
||||||
|
<trusted-key id="4021EEEAFF5DE8404DCD0A270AA3E5C3D232E79B">
|
||||||
|
<trusting group="jakarta.enterprise"/>
|
||||||
|
<trusting group="jakarta.inject"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="41D266DB4427983A1A4AFB0C3684155E9365C30E" group="com.jayway.jsonpath" name="json-path" version="2.9.0"/>
|
||||||
|
<trusted-key id="44FBDBBC1A00FE414F1C1873586654072EAD6677" group="org.sonatype.oss" name="oss-parent" version="9"/>
|
||||||
|
<trusted-key id="453EA31328DE7D8AAA55AD4ED56C721C1CFF1424" group="^com[.]twelvemonkeys($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="475F3B8E59E6E63AA78067482C7B12F2A511E325" group="org.slf4j"/>
|
||||||
|
<trusted-key id="477E62A656AD5475A1882855C809CA3C41BA6E96" group="jakarta.validation" name="jakarta.validation-api" version="3.0.2"/>
|
||||||
|
<trusted-key id="4797B4F5DCC46CEA61059071A1AE06236CA2BA62" group="^com[.]diffplug($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="47EF0EC60C210BC6DFAA5819B7AE15C15C321C44" group="jakarta.transaction" name="jakarta.transaction-api" version="2.0.1"/>
|
||||||
|
<trusted-key id="47FF105DF431FF5416B821FEAECDB81D38EA9C89" group="org.commonmark"/>
|
||||||
|
<trusted-key id="482C52BC305FB31063CD19D67BEFA2F0A9C24E7D" group="net.sf.launch4j" name="launch4j" version="3.50"/>
|
||||||
|
<trusted-key id="48B086A7D843CFA258E83286928FBF39003C0425">
|
||||||
|
<trusting group="io.micrometer"/>
|
||||||
|
<trusting group="io.projectreactor"/>
|
||||||
|
<trusting group="io.spring.gradle"/>
|
||||||
|
<trusting group="^org[.]springframework($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="498AAC354AA5CB36FAAB7608B6E83A2D2E447E56" group="org.apache.cassandra" name="java-driver-bom" version="4.18.1"/>
|
||||||
|
<trusted-key id="4F7E32D440EF90A83011A8FC6425559C47CC79C4">
|
||||||
|
<trusting group="com.sun.activation"/>
|
||||||
|
<trusting group="javax.activation"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="53C935821AA6A755BD337DB53595395EB3D8E1BA" group="org.apache.logging.log4j" name="log4j-bom" version="2.20.0"/>
|
||||||
|
<trusted-key id="5719E50EAC5A4B1DD390B72C2A742740E08E7F8D" group="org.antlr"/>
|
||||||
|
<trusted-key id="57312C37B064EE0FDAB0130490D5CE79E1DE6A2C" group="com.querydsl" name="querydsl-bom"/>
|
||||||
|
<trusted-key id="5989BAF76217B843D66BE55B2D0E1FB8FE4B68B4" group="^org[.]eclipse[.]jetty($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="59A8E169739301FD48139CA00E325BECB6962A24" group="jakarta.annotation" name="jakarta.annotation-api" version="2.1.1"/>
|
||||||
|
<trusted-key id="5C9A30FF22B2C02F30261C305B93F1DF7CDB6DEA" group="org.apache.xmlgraphics"/>
|
||||||
|
<trusted-key id="60200AC4AE761F1614D6C46766D68DAA073BE985">
|
||||||
|
<trusting group="ch.qos.logback"/>
|
||||||
|
<trusting group="org.slf4j"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="694621A7227D8D5289699830ABE9F3126BB741C1" group="com.google.guava" name="guava-parent" version="26.0-android"/>
|
||||||
|
<trusted-key id="6DD3B8C64EF75253BEB2C53AD908A43FB7EC07AC" group="jakarta.activation" name="jakarta.activation-api" version="2.1.3"/>
|
||||||
|
<trusted-key id="6E13156C0EE653F0B984663AB95BBD3FA43C4492" group="org.apache" name="apache" version="3"/>
|
||||||
|
<trusted-key id="6F538074CCEBF35F28AF9B066A0975F8B1127B83" group="org.jetbrains.kotlin"/>
|
||||||
|
<trusted-key id="70CD19BFD9F6C330027D6F260315BFB7970A144F" group="javax.xml.bind"/>
|
||||||
|
<trusted-key id="71EBF1CA4125B10AAB1E17CDB7DC526C17E3608B" group="jakarta.persistence" name="jakarta.persistence-api" version="3.1.0"/>
|
||||||
|
<trusted-key id="7464550A61C90BA385FC97A76D9567281201E5E3" group="jakarta.servlet" name="jakarta.servlet-api" version="6.0.0"/>
|
||||||
|
<trusted-key id="7616EB882DAF57A11477AAF559A252FB1199D873" group="com.google.code.findbugs" name="jsr305" version="3.0.2"/>
|
||||||
|
<trusted-key id="798E2DA37E70DAE0EA9E498CA388C395AAFB80F8" group="io.dropwizard.metrics"/>
|
||||||
|
<trusted-key id="7A1D848E7C2AF85EEBA69C99E7BF252CF360097E" group="org.latencyutils" name="LatencyUtils" version="2.0.3"/>
|
||||||
|
<trusted-key id="7B121B76A7ED6CE6E60AD51784E913A8E3A748C0" group="org.bouncycastle"/>
|
||||||
|
<trusted-key id="7C669810892CBD3148FA92995B05CCDE140C2876" group="org.eclipse.jgit"/>
|
||||||
|
<trusted-key id="808D78B17A5A2D7C3668E31FBFFC9B54721244AD" group="org.apache.commons" name="commons-parent" version="39"/>
|
||||||
|
<trusted-key id="80F6D6B0D90C6747753344CAB5A9E81B565E89E0" group="org.tomlj" name="tomlj" version="1.0.0"/>
|
||||||
|
<trusted-key id="81BE0C38ACE8AEDC7735A05F4C2AFF633F3A7223" group="org.seleniumhq.selenium" name="selenium-bom"/>
|
||||||
|
<trusted-key id="81CCDC71C7D61C179B27002D6A9FBE152D4C64D1" group="org.openjfx"/>
|
||||||
|
<trusted-key id="82F0964816AD7319CB0CCCF93EFD9D223D715E9A" group="com.nimbusds"/>
|
||||||
|
<trusted-key id="82F94BBDF95C247BBD21396B9A0B94DEC0FFA7EE" group="org.webjars" name="swagger-ui" version="5.2.0"/>
|
||||||
|
<trusted-key id="839323A4780D5BF9A6978970152888E10EF880B3">
|
||||||
|
<trusting group="org.attoparser"/>
|
||||||
|
<trusting group="org.unbescape"/>
|
||||||
|
<trusting group="^org[.]thymeleaf($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="84789D24DF77A32433CE1F079EB80E92EB2135B1" group="org.apache" name="apache"/>
|
||||||
|
<trusted-key id="8756C4F765C9AC3CB6B85D62379CE192D401AB61" group="com.diffplug.durian"/>
|
||||||
|
<trusted-key id="894F14D98D7F20D5E82645E3DFE102108BF9381F" group="org.hibernate.search" name="hibernate-search-bom" version="7.1.2.Final"/>
|
||||||
|
<trusted-key id="94976E17E18DD3201447286954963C3E875A56AE" group="io.smallrye"/>
|
||||||
|
<trusted-key id="9579802DC3E15DE9C389239FC0D48A119CE7EE7B" group="com.zaxxer" name="HikariCP" version="5.1.0"/>
|
||||||
|
<trusted-key id="9790B1EC52577244529621F38C77ED250E495230" group="com.bucket4j" name="bucket4j_jdk17-core" version="8.14.0"/>
|
||||||
|
<trusted-key id="982C26A0C156D986CC2AD19E3FBA8E8E719022D7" group="org.jboss" name="jboss-parent" version="39"/>
|
||||||
|
<trusted-key id="9B32CBC0F3F6BA4C13D611FC21871D2A9AB66A31" group="io.rsocket" name="rsocket-bom" version="1.1.3"/>
|
||||||
|
<trusted-key id="9E3044071B758EBCB7E45673700E4F39BC05364B">
|
||||||
|
<trusting group="org.eclipse.platform" name="org.eclipse.osgi" version="3.18.300"/>
|
||||||
|
<trusting group="org.eclipse.platform" name="org.eclipse.osgi" version="3.18.500"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="A41A5960555F8CBBC7D8B2D7787F3A057B828D36" group="org.springdoc"/>
|
||||||
|
<trusted-key id="A5BD02B93E7A40482EB1D66A5F69AD087600B22C" group="org.ow2.asm"/>
|
||||||
|
<trusted-key id="A602970FE1BF5C9C8A9491B97A3C9FE21DFDBF44" group="org.apache.pdfbox"/>
|
||||||
|
<trusted-key id="A654E2E6D97BE4219A4909415B15A33991BEA5A8" group="me.friwi"/>
|
||||||
|
<trusted-key id="A6D6C97108B8585F91B158748671A8DF71296252" group="com.squareup.okhttp3" name="okhttp-bom" version="4.10.0"/>
|
||||||
|
<trusted-key id="A7892505CF1A58076453E52D7999BEFBA1039E8B" group="net.bytebuddy"/>
|
||||||
|
<trusted-key id="A9789342F598AD5B1175EF357EB97D110DFADD60" group="com.googlecode.concurrent-trees" name="concurrent-trees" version="2.6.1"/>
|
||||||
|
<trusted-key id="AA70C7C433D501636392EC02153E7A3C2B4E5118" group="org.eclipse.ee4j" name="project"/>
|
||||||
|
<trusted-key id="AB1DC33940689C44669107094989E0E939C2999B">
|
||||||
|
<trusting group="com.opencsv" name="opencsv" version="5.10"/>
|
||||||
|
<trusting group="com.opencsv" name="opencsv" version="5.9"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="B1F250C1F371EBF0E31E86E30E31BBB30C940D01" group="com.posthog.java" name="posthog" version="1.1.1"/>
|
||||||
|
<trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314">
|
||||||
|
<trusting group="commons-beanutils"/>
|
||||||
|
<trusting group="org.apache.commons"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="BA926F64CA647B6D853A38672E2010F8A7FF4A41" group="org.apache" name="apache" version="7"/>
|
||||||
|
<trusted-key id="BB785E0400E71390977E4D1ADF3CC7C64D56297B" group="jakarta.interceptor" name="jakarta.interceptor-api" version="2.1.0"/>
|
||||||
|
<trusted-key id="BCA1F17506AF088F3A964A9C0459A2B383ED8C11" group="org.eclipse.angus"/>
|
||||||
|
<trusted-key id="BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE" group="^com[.]google($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="BE685132AFD2740D9095F9040CC0B712FEE75827" group="org.assertj"/>
|
||||||
|
<trusted-key id="C1D1ADA83198AA7FEAD102483FFE64C7506FCCC9" group="com.coveo" name="saml-client" version="5.0.0"/>
|
||||||
|
<trusted-key id="C663D2F64DA2CA09DB28D9ABD3FA67D522C55256" group="org.apache.pulsar" name="pulsar-bom" version="3.3.3"/>
|
||||||
|
<trusted-key id="C7BE5BCC9FEC15518CFDA882B0F3710FA64900E7" group="com.google.code.gson"/>
|
||||||
|
<trusted-key id="C89074FC8BE681B7C7EAAB6E4C5EED3C53B75933" group="org.skyscreamer" name="jsonassert" version="1.5.3"/>
|
||||||
|
<trusted-key id="CA62ED130E4053944406DF640181B45EA58677BC" group="org.apache.logging" name="logging-parent" version="7"/>
|
||||||
|
<trusted-key id="CC57399D74CD7E4768ED6FA4CA62973FBF0451C0" group="com.vaadin.external.google" name="android-json" version="0.0.20131108.vaadin1"/>
|
||||||
|
<trusted-key id="CD5464315F0B98C77E6E8ECD9DAADC1C9FCC82D0" group="commons-cli" name="commons-cli" version="1.4"/>
|
||||||
|
<trusted-key id="CE3285F320685193D11FEA01F6CE9695C9318406" group="com.google.zxing"/>
|
||||||
|
<trusted-key id="CE4439C1BEF3DA83B1832F9DBEFEEF227A98B809" group="org.apache.velocity"/>
|
||||||
|
<trusted-key id="CE8075A251547BEE249BC151A2115AE15F6B8B72">
|
||||||
|
<trusting group="org.apache.commons"/>
|
||||||
|
<trusting group="org.xmlunit"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="D421D1DF4570BFB13E485D0BF95ADD0A28D2F139" group="org.projectlombok" name="lombok" version="1.18.36"/>
|
||||||
|
<trusted-key id="D54A395B5CF3F86EB45F6E426B1B008864323B92" group="org.antlr"/>
|
||||||
|
<trusted-key id="DB45ECD19B97514F727105AE67BF80B10AD53983" group="org.apache.santuario" name="xmlsec" version="2.3.4"/>
|
||||||
|
<trusted-key id="DBD744ACE7ADE6AA50DD591F66B50994442D2D40" group="^com[.]squareup($|([.].*))" regex="true"/>
|
||||||
|
<trusted-key id="DBFBFF8DA2F1571ACC6F63AB905CF8FC70CC1444" group="org.aspectj" name="aspectjweaver" version="1.9.22.1"/>
|
||||||
|
<trusted-key id="DCAA15007BED9DE690CD9523378B845402277962">
|
||||||
|
<trusting group="org.opensaml"/>
|
||||||
|
<trusting group="^net[.]shibboleth($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="E01AAB301618D23B39DBD41002DE09238A0E4D34" group="com.drewnoakes" name="metadata-extractor" version="2.19.0"/>
|
||||||
|
<trusted-key id="E113159331A1F87BFC2A93D0960D2E8635A91268" group="org.hdrhistogram" name="HdrHistogram" version="2.2.2"/>
|
||||||
|
<trusted-key id="E2ACB037933CDEAAB7BF77D49A2C7A98E457C53D">
|
||||||
|
<trusting group="io.projectreactor"/>
|
||||||
|
<trusting group="^org[.]springframework($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="E3A9F95079E84CE201F7CF60BEDE11EAF1164480" group="org.hamcrest" name="hamcrest"/>
|
||||||
|
<trusted-key id="E7DC75FC24FB3C8DFE8086AD3D5839A2262CBBFB" group="org.jetbrains.kotlinx"/>
|
||||||
|
<trusted-key id="E85AED155021AF8A6C6B7A4A7C7D8456294423BA" group="org.objenesis"/>
|
||||||
|
<trusted-key id="EE0CA873074092F806F59B65D364ABAA39A47320">
|
||||||
|
<trusting group="com.google.errorprone"/>
|
||||||
|
<trusting group="com.google.googlejavaformat"/>
|
||||||
|
</trusted-key>
|
||||||
|
<trusted-key id="EED29BAB8D4FD882D62308CD72D1B04BC7E6AA04" group="me.friwi"/>
|
||||||
|
<trusted-key id="EF5214AD654CD05F0DA91609ECEAC3B11AD0E0A0" group="com.adobe.xmp" name="xmpcore" version="6.1.11"/>
|
||||||
|
<trusted-key id="F046369B06B761AC86D9849F71B329993BFFCFDD" group="com.oracle.database.jdbc" name="ojdbc-bom" version="21.9.0.0"/>
|
||||||
|
<trusted-key id="F0E31196852A34F8855710BD4A6CE7EBC7F4F54B" group="io.prometheus"/>
|
||||||
|
<trusted-key id="F1232CDCD94176E7FBA9CFE289A2C76A5EE16E57" group="technology.tabula" name="tabula" version="1.0.5"/>
|
||||||
|
<trusted-key id="F3184BCD55F4D016E30D4C9BF42E87F9665015C9" group="org.jsoup" name="jsoup" version="1.18.3"/>
|
||||||
|
<trusted-key id="F55EF5BB19F52A250FEDC0DF39450183608E49D4" group="com.googlecode.owasp-java-html-sanitizer"/>
|
||||||
|
<trusted-key id="F5FEBA84EB26C56457B2CF819E31AB27445478DB" group="org.infinispan"/>
|
||||||
|
<trusted-key id="F60649A7F36F9FBEE21D9AA08AC0378EC753063D" group="com.fathzer"/>
|
||||||
|
<trusted-key id="F674EBA7B6EC777BDB58942DE0E92C40A43A012A" group="jakarta.websocket"/>
|
||||||
|
<trusted-key id="FA77DCFEF2EE6EB2DEBEDD2C012579464D01C06A" group="org.apache" name="apache"/>
|
||||||
|
<trusted-key id="FA7929F83AD44C4590F6CC6815C71C0A4E0B8EDD" group="net.java.dev.jna"/>
|
||||||
|
<trusted-key id="FC411CD3CB7DCB0ABC9801058118B3BCDB1A5000" group="jakarta.xml.bind"/>
|
||||||
|
<trusted-key id="FF6E2C001948C5F2F38B0CC385911F425EC61B51">
|
||||||
|
<trusting group="org.apiguardian"/>
|
||||||
|
<trusting group="org.opentest4j"/>
|
||||||
|
<trusting group="^org[.]junit($|([.].*))" regex="true"/>
|
||||||
|
</trusted-key>
|
||||||
|
</trusted-keys>
|
||||||
|
</configuration>
|
||||||
|
<components>
|
||||||
|
<component group="com.diffplug.spotless" name="com.diffplug.spotless.gradle.plugin" version="6.25.0">
|
||||||
|
<artifact name="com.diffplug.spotless.gradle.plugin-6.25.0.pom">
|
||||||
|
<sha256 value="f45c82b12faacd85acd474eba699322fa5dea88408b247d0e4bde9412908223a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.diffplug.spotless" name="com.diffplug.spotless.gradle.plugin" version="7.0.1">
|
||||||
|
<artifact name="com.diffplug.spotless.gradle.plugin-7.0.1.pom">
|
||||||
|
<sha256 value="d967a0f74c203ddcc5700947aab40f4be2a5a9f7b8d32aab7fc412b2030e7dfc" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.Carleslc.Simple-YAML" name="Simple-Configuration" version="1.8.4">
|
||||||
|
<artifact name="Simple-Configuration-1.8.4.jar">
|
||||||
|
<sha256 value="2b960f4840ac68bb1815d937ca2d58eb9b04c05e6a9b769a4e870c52a4728156" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="Simple-Configuration-1.8.4.pom">
|
||||||
|
<sha256 value="698e378e816a220edfcb754fd4c4f7d9a8fd38716b9081f63f9878d4bbf3cdd5" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.Carleslc.Simple-YAML" name="Simple-YAML-Parent" version="1.8.4">
|
||||||
|
<artifact name="Simple-YAML-Parent-1.8.4.pom">
|
||||||
|
<sha256 value="b9298b875185bd13b4e301187eeb234d3a1a4b1a871dd4a7461f2e7775121357" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.Carleslc.Simple-YAML" name="Simple-Yaml" version="1.8.4">
|
||||||
|
<artifact name="Simple-Yaml-1.8.4.jar">
|
||||||
|
<sha256 value="d558ca57927d4bc393e9522aac0cf60cc632a9f6f60cd6724aa94b7005e1fd18" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="Simple-Yaml-1.8.4.pom">
|
||||||
|
<sha256 value="47f1003cd91eb6c11b2c941bf89e72428aed92e6bfef327b18935dda28eb4072" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.jk1" name="gradle-license-report" version="2.9">
|
||||||
|
<artifact name="gradle-license-report-2.9.jar">
|
||||||
|
<sha256 value="ebfd6da851654c53216eea9eda1485c12e0cd6de5a9919bf5da9735a021f32af" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="gradle-license-report-2.9.module">
|
||||||
|
<sha256 value="4139a508481c369ae0f2627fa8387f1e20e58600f2037cdc1cdaa164e056f235" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.jk1.dependency-license-report" name="com.github.jk1.dependency-license-report.gradle.plugin" version="2.9">
|
||||||
|
<artifact name="com.github.jk1.dependency-license-report.gradle.plugin-2.9.pom">
|
||||||
|
<sha256 value="a79ca4dfe069d737faf075c8f4b6c6471c2e5cea8e1546946ae333d747fddf02" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.github.psxpaul" name="gradle-execfork-plugin" version="0.2.0">
|
||||||
|
<artifact name="gradle-execfork-plugin-0.2.0.jar">
|
||||||
|
<sha256 value="eb4f73df13ee24fb1952e0a9054c5618ef07f0d62386bfad1a04990df0cb3a65" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="gradle-execfork-plugin-0.2.0.module">
|
||||||
|
<sha256 value="7b239eb029b2e4cab00dddf1df204ef4bbf88e78a43619c26fbb1e49bc53c642" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.google.guava" name="guava-parent" version="33.3.1-jre">
|
||||||
|
<artifact name="guava-parent-33.3.1-jre.pom">
|
||||||
|
<sha256 value="55441db27e8869dfefe053059bdf478bdc7e95585642bf391f0023345fd56287" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.martiansoftware" name="jsap" version="2.1">
|
||||||
|
<artifact name="jsap-2.1.jar">
|
||||||
|
<sha256 value="331746fa62cfbc3368260c5a2e660936ad11be612308c120a044e120361d474e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="jsap-2.1.pom">
|
||||||
|
<sha256 value="9acf56a8579c05bedd819d99232363e2bf327e8f73c67598dbd9885a845a3c69" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="commons-logging" name="commons-logging" version="1.0.4">
|
||||||
|
<artifact name="commons-logging-1.0.4.pom">
|
||||||
|
<sha256 value="65d310509352b5425118225ee600a01f83ba72142d035014b5d164bc04b2d284" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="edu.sc.seis.launch4j" name="edu.sc.seis.launch4j.gradle.plugin" version="3.0.6">
|
||||||
|
<artifact name="edu.sc.seis.launch4j.gradle.plugin-3.0.6.pom">
|
||||||
|
<sha256 value="62a4f6752190b9ebf30869e092e4154e41a2c5cd96048ae98a01916f2684465a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="edu.sc.seis.launch4j" name="launch4j" version="3.0.6">
|
||||||
|
<artifact name="launch4j-3.0.6.jar">
|
||||||
|
<sha256 value="6a8f000c6fda2eb17406b516ec0be28cdac900cbba03319e57bd3c2f1b1afa02" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="launch4j-3.0.6.module">
|
||||||
|
<sha256 value="0a38e1daab79a32b56790db458088148c97be021764e2d2dce259b9a87fec048" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="io.dropwizard.metrics" name="metrics-bom" version="4.2.25">
|
||||||
|
<artifact name="metrics-bom-4.2.25.pom">
|
||||||
|
<sha256 value="825ad37b8380f992b515050bbd95452f85466feae7b856d5c150d4e5f716a8e9" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="io.dropwizard.metrics" name="metrics-parent" version="4.2.25">
|
||||||
|
<artifact name="metrics-parent-4.2.25.pom">
|
||||||
|
<sha256 value="df7b6371f9b15698e123d9861f2099ca32c9ec966d9f0c60755a2a34ccbfabc2" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="io.spring.dependency-management" name="io.spring.dependency-management.gradle.plugin" version="1.1.7">
|
||||||
|
<artifact name="io.spring.dependency-management.gradle.plugin-1.1.7.pom">
|
||||||
|
<sha256 value="19bb16ab5d6359bff88ce95c80b01e7e3445157faa1d74ae5cf03a467cea1e04" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="io.swagger" name="swaggerhub" version="1.3.2">
|
||||||
|
<artifact name="swaggerhub-1.3.2.jar">
|
||||||
|
<sha256 value="703a61e96b23af81b2ceeba4a081bb3212ec00211ae300748b3e7ccb1f33bd32" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="swaggerhub-1.3.2.module">
|
||||||
|
<sha256 value="bd5ccd6e48224cab88bbd79e880ff011ee4fa711490f410f7810b75a2cb9c3c0" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="io.swagger.swaggerhub" name="io.swagger.swaggerhub.gradle.plugin" version="1.3.2">
|
||||||
|
<artifact name="io.swagger.swaggerhub.gradle.plugin-1.3.2.pom">
|
||||||
|
<sha256 value="69069eee12440c521662057334ac4acaea0ce6534ca4fd8b1bc264de930ad2d0" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="net.java" name="jvnet-parent" version="5">
|
||||||
|
<artifact name="jvnet-parent-5.pom">
|
||||||
|
<sha256 value="1af699f8d9ddab67f9a0d202fbd7915eb0362a5a6dfd5ffc54cafa3465c9cb0a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="net.shibboleth" name="parent" version="11.3.5">
|
||||||
|
<artifact name="parent-11.3.5.pom">
|
||||||
|
<pgp value="0E0CA56D354132B5E646C25F49A1796B9B494CB8"/>
|
||||||
|
<sha256 value="7a24e2700485eea087370f1dca6fe0291d7893d38c11aabfe977784fd93b808c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.apache" name="apache" version="27">
|
||||||
|
<artifact name="apache-27.pom">
|
||||||
|
<sha256 value="b2b0fc69e22a650c3892f1c366d77076f29575c6738df4c7a70a44844484cdf9" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.apache" name="apache" version="33">
|
||||||
|
<artifact name="apache-33.pom">
|
||||||
|
<sha256 value="d78bd8524c5f8380a190a6525686629a95dfe512df21111383a6d8c0923a4415" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.apache.commons" name="commons-parent" version="78">
|
||||||
|
<artifact name="commons-parent-78.pom">
|
||||||
|
<sha256 value="022d202e655edd04f2a10ecbe453d92977924d38380a4ca8c359f1817a80320e" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.hibernate.common" name="hibernate-commons-annotations" version="7.0.3.Final">
|
||||||
|
<artifact name="hibernate-commons-annotations-7.0.3.Final.jar">
|
||||||
|
<sha256 value="0db2fd57d5e43688ac6ed5cdf36deaf05d84340dcc24c2dd2a2114de38e5175d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="hibernate-commons-annotations-7.0.3.Final.module">
|
||||||
|
<sha256 value="b1aa7202fc3f67d22066903d3e1eb7052ee10f47322a1cb925fa2f449f25aee3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="hibernate-commons-annotations-7.0.3.Final.pom">
|
||||||
|
<sha256 value="66f6e607b30740e391989825a5ae076a6c877a99b78eb054a8146650aaff72eb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.jboss" name="jboss-parent" version="42">
|
||||||
|
<artifact name="jboss-parent-42.pom">
|
||||||
|
<sha256 value="e41276efe3509054cba4197b3d6360c51dd57bc640dde48cf37dafaa45a09c3b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.jboss" name="jboss-parent" version="43">
|
||||||
|
<artifact name="jboss-parent-43.pom">
|
||||||
|
<sha256 value="3c3ade76fb883acdb9a8a03355d1df4066ffb9c8c78f09b219e9c0fc2a3f4317" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.jboss.logging" name="jboss-logging" version="3.6.1.Final">
|
||||||
|
<artifact name="jboss-logging-3.6.1.Final.jar">
|
||||||
|
<sha256 value="5e08a4b092dc85b337f0910a740571d8720cfa565fabd880a8caf94a657ca416" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="jboss-logging-3.6.1.Final.pom">
|
||||||
|
<sha256 value="27cd88ab8e5946b8a7aa92644eb3732e35be281439ab07af71f898453ee7540d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.jboss.logging" name="logging-parent" version="1.0.3.Final">
|
||||||
|
<artifact name="logging-parent-1.0.3.Final.pom">
|
||||||
|
<sha256 value="9972c894749cda355766217d43ded7009b1eeb26e0301c30914a2db253dd685b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.junit" name="junit-bom" version="5.11.2">
|
||||||
|
<artifact name="junit-bom-5.11.2.pom">
|
||||||
|
<sha256 value="f48e88538aac145eb3ae0345a9ebd055b28f329a35dce8d1e9281325ca9b0ea2" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.opensaml" name="opensaml-bom" version="4.3.0">
|
||||||
|
<artifact name="opensaml-bom-4.3.0.pom">
|
||||||
|
<sha256 value="4dfcc7cd96a2645c6e28df9f166f0e5b2b1a44aa109b3100cdb0ee17e01e02d2" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.opensaml" name="opensaml-parent" version="4.3.0">
|
||||||
|
<artifact name="opensaml-parent-4.3.0.pom">
|
||||||
|
<sha256 value="5e9db2f2dc3938835a76f5334997d79c8781511c8b68c1f6df6b384306900319" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.ow2" name="ow2" version="1.5.1">
|
||||||
|
<artifact name="ow2-1.5.1.pom">
|
||||||
|
<sha256 value="321ddbb7ee6fe4f53dea6b4cd6db74154d6bfa42391c1f763b361b9f485acf05" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.panteleyev" name="jpackage-gradle-plugin" version="1.6.0">
|
||||||
|
<artifact name="jpackage-gradle-plugin-1.6.0.jar">
|
||||||
|
<sha256 value="a8a588ff44a62db1aee62d3da117d2632a7f9a107709ca201da2a59dcb500175" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="jpackage-gradle-plugin-1.6.0.module">
|
||||||
|
<sha256 value="a572bc67a0bcce5eb8c50a0ae2659fba850dae5b0188f53045635c9276545179" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.panteleyev.jpackageplugin" name="org.panteleyev.jpackageplugin.gradle.plugin" version="1.6.0">
|
||||||
|
<artifact name="org.panteleyev.jpackageplugin.gradle.plugin-1.6.0.pom">
|
||||||
|
<sha256 value="82bff05e70c9f7f5c3d4a8c3958b3842dea970ac1378e306051e66cf98a8f340" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.sonatype.oss" name="oss-parent" version="5">
|
||||||
|
<artifact name="oss-parent-5.pom">
|
||||||
|
<pgp value="2BCBDD0F23EA1CAFCC11D4860374CF2E8DD1BDFD"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.sonatype.oss" name="oss-parent" version="7">
|
||||||
|
<artifact name="oss-parent-7.pom">
|
||||||
|
<sha256 value="b51f8867c92b6a722499557fc3a1fdea77bdf9ef574722fe90ce436a29559454" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.springdoc" name="springdoc-openapi-gradle-plugin" version="1.8.0">
|
||||||
|
<artifact name="springdoc-openapi-gradle-plugin-1.8.0.jar">
|
||||||
|
<sha256 value="94075aa01757a0c1d573ade9145c098963f084feefd31dc95d65606c503585f4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="springdoc-openapi-gradle-plugin-1.8.0.module">
|
||||||
|
<sha256 value="6f2d828807961169293f4f5b897f7c3ee76da06dc6bb9d94acb3d5f2418e998c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.springdoc.openapi-gradle-plugin" name="org.springdoc.openapi-gradle-plugin.gradle.plugin" version="1.8.0">
|
||||||
|
<artifact name="org.springdoc.openapi-gradle-plugin.gradle.plugin-1.8.0.pom">
|
||||||
|
<sha256 value="2d117343231e29be22e489bc1f4825e1d4bbef545905e793244e95221957154f" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.springframework" name="spring-framework-bom" version="5.3.34">
|
||||||
|
<artifact name="spring-framework-bom-5.3.34.pom">
|
||||||
|
<sha256 value="6d0616e2544d7115dc249817dd758a34dfa677329182b42e17542e133e55732d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="org.springframework.boot" name="org.springframework.boot.gradle.plugin" version="3.4.1">
|
||||||
|
<artifact name="org.springframework.boot.gradle.plugin-3.4.1.pom">
|
||||||
|
<sha256 value="f4d1acf98aa55a44b1a23b1c2b5c75aaa84c4408bea955bd81efb38acd68f38d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="xml-apis" name="xml-apis-ext" version="1.3.04">
|
||||||
|
<artifact name="xml-apis-ext-1.3.04.jar">
|
||||||
|
<sha256 value="d0b4887dc34d57de49074a58affad439a013d0baffa1a8034f8ef2a5ea191646" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="xml-apis-ext-1.3.04.pom">
|
||||||
|
<sha256 value="1b5939a9310a59c0df0c03726721d5fc9521e87d6c203bfa7220bae82a30d9e8" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="xmlpull" name="xmlpull" version="1.1.3.1">
|
||||||
|
<artifact name="xmlpull-1.1.3.1.jar">
|
||||||
|
<sha256 value="34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="xmlpull-1.1.3.1.pom">
|
||||||
|
<sha256 value="8f10ffd8df0d3e9819c8cc8402709c6b248bc53a954ef6e45470d9ae3a5735fb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
</components>
|
||||||
|
</verification-metadata>
|
||||||
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:
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ ignore = [
|
|||||||
'validateSignature.cert.version',
|
'validateSignature.cert.version',
|
||||||
'validateSignature.status',
|
'validateSignature.status',
|
||||||
'watermark.type.1',
|
'watermark.type.1',
|
||||||
|
'redact.zoom',
|
||||||
]
|
]
|
||||||
|
|
||||||
[el_GR]
|
[el_GR]
|
||||||
@@ -247,6 +248,11 @@ ignore = [
|
|||||||
'showJS.tags',
|
'showJS.tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[zh_BO]
|
||||||
|
ignore = [
|
||||||
|
'language.direction',
|
||||||
|
]
|
||||||
|
|
||||||
[zh_CN]
|
[zh_CN]
|
||||||
ignore = [
|
ignore = [
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class LicenseKeyChecker {
|
|||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private boolean enterpriseEnbaledResult = false;
|
private boolean enterpriseEnabledResult = false;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public LicenseKeyChecker(
|
public LicenseKeyChecker(
|
||||||
@@ -35,12 +35,12 @@ public class LicenseKeyChecker {
|
|||||||
|
|
||||||
private void checkLicense() {
|
private void checkLicense() {
|
||||||
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
|
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||||
enterpriseEnbaledResult = false;
|
enterpriseEnabledResult = false;
|
||||||
} else {
|
} else {
|
||||||
enterpriseEnbaledResult =
|
enterpriseEnabledResult =
|
||||||
licenseService.verifyLicense(
|
licenseService.verifyLicense(
|
||||||
applicationProperties.getEnterpriseEdition().getKey());
|
applicationProperties.getEnterpriseEdition().getKey());
|
||||||
if (enterpriseEnbaledResult) {
|
if (enterpriseEnabledResult) {
|
||||||
log.info("License key is valid.");
|
log.info("License key is valid.");
|
||||||
} else {
|
} else {
|
||||||
log.info("License key is invalid.");
|
log.info("License key is invalid.");
|
||||||
@@ -55,6 +55,6 @@ public class LicenseKeyChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getEnterpriseEnabledResult() {
|
public boolean getEnterpriseEnabledResult() {
|
||||||
return enterpriseEnbaledResult;
|
return enterpriseEnabledResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package stirling.software.SPDF;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -24,15 +25,17 @@ import jakarta.annotation.PreDestroy;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.UI.WebBrowser;
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableScheduling
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SPdfApplication {
|
@EnableScheduling
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SPDFApplication {
|
||||||
|
|
||||||
private static String baseUrlStatic;
|
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
private static String baseUrlStatic;
|
||||||
|
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final WebBrowser webBrowser;
|
private final WebBrowser webBrowser;
|
||||||
@@ -40,7 +43,7 @@ public class SPdfApplication {
|
|||||||
@Value("${baseUrl:http://localhost}")
|
@Value("${baseUrl:http://localhost}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
|
||||||
public SPdfApplication(
|
public SPDFApplication(
|
||||||
Environment env,
|
Environment env,
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@Autowired(required = false) WebBrowser webBrowser) {
|
@Autowired(required = false) WebBrowser webBrowser) {
|
||||||
@@ -49,42 +52,41 @@ public class SPdfApplication {
|
|||||||
this.webBrowser = webBrowser;
|
this.webBrowser = webBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
|
||||||
private static String findAvailablePort(int startPort) {
|
|
||||||
int port = startPort;
|
|
||||||
while (!isPortAvailable(port)) {
|
|
||||||
port++;
|
|
||||||
}
|
|
||||||
return String.valueOf(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPortAvailable(int port) {
|
|
||||||
try (ServerSocket socket = new ServerSocket(port)) {
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
SpringApplication app = new SpringApplication(SPDFApplication.class);
|
||||||
|
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
|
|
||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
System.setProperty("java.awt.headless", "false");
|
System.setProperty("java.awt.headless", "false");
|
||||||
app.setHeadless(false);
|
app.setHeadless(false);
|
||||||
props.put("java.awt.headless", "false");
|
props.put("java.awt.headless", "false");
|
||||||
props.put("spring.main.web-application-type", "servlet");
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
}
|
}
|
||||||
app.setAdditionalProfiles("default");
|
|
||||||
app.addInitializers(new ConfigInitializer());
|
app.setAdditionalProfiles(getActiveProfile(args));
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
|
||||||
// External config files
|
ConfigInitializer initializer = new ConfigInitializer();
|
||||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
try {
|
||||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
initializer.ensureConfigExists();
|
||||||
} else {
|
} catch (IOException | URISyntaxException e) {
|
||||||
log.warn("External configuration file 'configs/settings.yml' does not exist.");
|
log.error("Error initialising configuration", e);
|
||||||
}
|
}
|
||||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
|
// External config files
|
||||||
|
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath());
|
||||||
|
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) {
|
||||||
|
propertyFiles.put(
|
||||||
|
"spring.config.additional-location",
|
||||||
|
"file:" + InstallationPathConfig.getSettingsPath());
|
||||||
|
} else {
|
||||||
|
log.warn(
|
||||||
|
"External configuration file '{}' does not exist.",
|
||||||
|
InstallationPathConfig.getSettingsPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) {
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
if (!existingLocation.isEmpty()) {
|
if (!existingLocation.isEmpty()) {
|
||||||
@@ -92,57 +94,39 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
propertyFiles.put(
|
propertyFiles.put(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
existingLocation + "file:configs/custom_settings.yml");
|
existingLocation + "file:" + InstallationPathConfig.getCustomSettingsPath());
|
||||||
} else {
|
} else {
|
||||||
log.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
log.warn(
|
||||||
|
"Custom configuration file '{}' does not exist.",
|
||||||
|
InstallationPathConfig.getCustomSettingsPath());
|
||||||
}
|
}
|
||||||
Properties finalProps = new Properties();
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
if (!propertyFiles.isEmpty()) {
|
if (!propertyFiles.isEmpty()) {
|
||||||
finalProps.putAll(
|
finalProps.putAll(
|
||||||
Collections.singletonMap(
|
Collections.singletonMap(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
propertyFiles.get("spring.config.additional-location")));
|
propertyFiles.get("spring.config.additional-location")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.isEmpty()) {
|
if (!props.isEmpty()) {
|
||||||
finalProps.putAll(props);
|
finalProps.putAll(props);
|
||||||
}
|
}
|
||||||
app.setDefaultProperties(finalProps);
|
app.setDefaultProperties(finalProps);
|
||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
// Ensure directories are created
|
// Ensure directories are created
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of("customFiles/static/"));
|
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
|
||||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error creating directories: {}", e.getMessage());
|
log.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
printStartupLogs();
|
printStartupLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printStartupLogs() {
|
|
||||||
log.info("Stirling-PDF Started.");
|
|
||||||
String url = baseUrlStatic + ":" + getStaticPort();
|
|
||||||
log.info("Navigate to {}", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStaticPort() {
|
|
||||||
return serverPortStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
|
||||||
public void setServerPortStatic(String port) {
|
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
|
||||||
SPdfApplication.serverPortStatic = // This will let Spring Boot assign an available port
|
|
||||||
"0";
|
|
||||||
} else {
|
|
||||||
SPdfApplication.serverPortStatic = port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
baseUrlStatic = this.baseUrl;
|
baseUrlStatic = this.baseUrl;
|
||||||
@@ -173,6 +157,17 @@ public class SPdfApplication {
|
|||||||
log.info("Running configs {}", applicationProperties.toString());
|
log.info("Running configs {}", applicationProperties.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value("${server.port:8080}")
|
||||||
|
public void setServerPortStatic(String port) {
|
||||||
|
if ("auto".equalsIgnoreCase(port)) {
|
||||||
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
|
SPDFApplication.serverPortStatic =
|
||||||
|
"0"; // This will let Spring Boot assign an available port
|
||||||
|
} else {
|
||||||
|
SPDFApplication.serverPortStatic = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
if (webBrowser != null) {
|
if (webBrowser != null) {
|
||||||
@@ -180,10 +175,55 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void printStartupLogs() {
|
||||||
|
log.info("Stirling-PDF Started.");
|
||||||
|
String url = baseUrlStatic + ":" + getStaticPort();
|
||||||
|
log.info("Navigate to {}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] getActiveProfile(String[] args) {
|
||||||
|
if (args == null) {
|
||||||
|
return new String[] {"default"};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg.contains("spring.profiles.active")) {
|
||||||
|
return arg.substring(args[0].indexOf('=') + 1).split(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String[] {"default"};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPortAvailable(int port) {
|
||||||
|
try (ServerSocket socket = new ServerSocket(port)) {
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||||
|
private static String findAvailablePort(int startPort) {
|
||||||
|
int port = startPort;
|
||||||
|
while (!isPortAvailable(port)) {
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStaticBaseUrl() {
|
||||||
|
return baseUrlStatic;
|
||||||
|
}
|
||||||
|
|
||||||
public String getNonStaticBaseUrl() {
|
public String getNonStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getStaticPort() {
|
||||||
|
return serverPortStatic;
|
||||||
|
}
|
||||||
|
|
||||||
public String getNonStaticPort() {
|
public String getNonStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,7 @@ import me.friwi.jcefmaven.EnumProgress;
|
|||||||
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||||
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||||
import stirling.software.SPDF.UI.WebBrowser;
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -72,7 +73,8 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
CefAppBuilder builder = new CefAppBuilder();
|
CefAppBuilder builder = new CefAppBuilder();
|
||||||
configureCefSettings(builder);
|
configureCefSettings(builder);
|
||||||
builder.setProgressHandler(createProgressHandler());
|
builder.setProgressHandler(createProgressHandler());
|
||||||
|
builder.setInstallDir(
|
||||||
|
new File(InstallationPathConfig.getClientWebUIPath()));
|
||||||
// Build and initialize CEF
|
// Build and initialize CEF
|
||||||
cefApp = builder.build();
|
cefApp = builder.build();
|
||||||
client = cefApp.createClient();
|
client = cefApp.createClient();
|
||||||
@@ -99,8 +101,16 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
|
|
||||||
private void configureCefSettings(CefAppBuilder builder) {
|
private void configureCefSettings(CefAppBuilder builder) {
|
||||||
CefSettings settings = builder.getCefSettings();
|
CefSettings settings = builder.getCefSettings();
|
||||||
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
|
String basePath = InstallationPathConfig.getClientWebUIPath();
|
||||||
settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
|
log.info("basePath " + basePath);
|
||||||
|
settings.cache_path = new File(basePath + "cache").getAbsolutePath();
|
||||||
|
settings.root_cache_path = new File(basePath + "root_cache").getAbsolutePath();
|
||||||
|
// settings.browser_subprocess_path = new File(basePath +
|
||||||
|
// "subprocess").getAbsolutePath();
|
||||||
|
// settings.resources_dir_path = new File(basePath + "resources").getAbsolutePath();
|
||||||
|
// settings.locales_dir_path = new File(basePath + "locales").getAbsolutePath();
|
||||||
|
settings.log_file = new File(basePath, "debug.log").getAbsolutePath();
|
||||||
|
|
||||||
settings.persist_session_cookies = true;
|
settings.persist_session_cookies = true;
|
||||||
settings.windowless_rendering_enabled = false;
|
settings.windowless_rendering_enabled = false;
|
||||||
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
|
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
|
||||||
@@ -212,6 +222,9 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupLoadHandler() {
|
private void setupLoadHandler() {
|
||||||
|
final long initStartTime = System.currentTimeMillis();
|
||||||
|
log.info("Setting up load handler at: {}", initStartTime);
|
||||||
|
|
||||||
client.addLoadHandler(
|
client.addLoadHandler(
|
||||||
new CefLoadHandlerAdapter() {
|
new CefLoadHandlerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -220,32 +233,77 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
boolean isLoading,
|
boolean isLoading,
|
||||||
boolean canGoBack,
|
boolean canGoBack,
|
||||||
boolean canGoForward) {
|
boolean canGoForward) {
|
||||||
|
log.debug(
|
||||||
|
"Loading state change - isLoading: {}, canGoBack: {}, canGoForward: {}, "
|
||||||
|
+ "browserInitialized: {}, Time elapsed: {}ms",
|
||||||
|
isLoading,
|
||||||
|
canGoBack,
|
||||||
|
canGoForward,
|
||||||
|
browserInitialized,
|
||||||
|
System.currentTimeMillis() - initStartTime);
|
||||||
|
|
||||||
if (!isLoading && !browserInitialized) {
|
if (!isLoading && !browserInitialized) {
|
||||||
|
log.info(
|
||||||
|
"Browser finished loading, preparing to initialize UI components");
|
||||||
browserInitialized = true;
|
browserInitialized = true;
|
||||||
SwingUtilities.invokeLater(
|
SwingUtilities.invokeLater(
|
||||||
() -> {
|
() -> {
|
||||||
if (loadingWindow != null) {
|
try {
|
||||||
Timer timer =
|
if (loadingWindow != null) {
|
||||||
new Timer(
|
log.info("Starting UI initialization sequence");
|
||||||
500,
|
|
||||||
e -> {
|
|
||||||
loadingWindow.dispose();
|
|
||||||
loadingWindow = null;
|
|
||||||
|
|
||||||
frame.dispose();
|
// Close loading window first
|
||||||
frame.setOpacity(1.0f);
|
loadingWindow.setVisible(false);
|
||||||
frame.setUndecorated(false);
|
loadingWindow.dispose();
|
||||||
frame.pack();
|
loadingWindow = null;
|
||||||
frame.setSize(1280, 800);
|
log.info("Loading window disposed");
|
||||||
frame.setLocationRelativeTo(null);
|
|
||||||
frame.setVisible(true);
|
// Then setup the main frame
|
||||||
frame.requestFocus();
|
frame.setVisible(false);
|
||||||
frame.toFront();
|
frame.dispose();
|
||||||
browser.getUIComponent()
|
frame.setOpacity(1.0f);
|
||||||
.requestFocus();
|
frame.setUndecorated(false);
|
||||||
});
|
frame.pack();
|
||||||
timer.setRepeats(false);
|
frame.setSize(1280, 800);
|
||||||
timer.start();
|
frame.setLocationRelativeTo(null);
|
||||||
|
log.debug("Frame reconfigured");
|
||||||
|
|
||||||
|
// Show the main frame
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.requestFocus();
|
||||||
|
frame.toFront();
|
||||||
|
log.info("Main frame displayed and focused");
|
||||||
|
|
||||||
|
// Focus the browser component
|
||||||
|
Timer focusTimer =
|
||||||
|
new Timer(
|
||||||
|
100,
|
||||||
|
e -> {
|
||||||
|
try {
|
||||||
|
browser.getUIComponent()
|
||||||
|
.requestFocus();
|
||||||
|
log.info(
|
||||||
|
"Browser component focused");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error(
|
||||||
|
"Error focusing browser",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
focusTimer.setRepeats(false);
|
||||||
|
focusTimer.start();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error during UI initialization", e);
|
||||||
|
// Attempt cleanup on error
|
||||||
|
if (loadingWindow != null) {
|
||||||
|
loadingWindow.dispose();
|
||||||
|
loadingWindow = null;
|
||||||
|
}
|
||||||
|
if (frame != null) {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.requestFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ public class LoadingWindow extends JDialog {
|
|||||||
private final JLabel statusLabel;
|
private final JLabel statusLabel;
|
||||||
private final JPanel mainPanel;
|
private final JPanel mainPanel;
|
||||||
private final JLabel brandLabel;
|
private final JLabel brandLabel;
|
||||||
|
private long startTime;
|
||||||
|
|
||||||
public LoadingWindow(Frame parent, String initialUrl) {
|
public LoadingWindow(Frame parent, String initialUrl) {
|
||||||
super(parent, "Initializing Stirling-PDF", true);
|
super(parent, "Initializing Stirling-PDF", true);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
log.info("Creating LoadingWindow - initialization started at: {}", startTime);
|
||||||
|
|
||||||
// Initialize components
|
// Initialize components
|
||||||
mainPanel = new JPanel();
|
mainPanel = new JPanel();
|
||||||
@@ -29,8 +32,8 @@ public class LoadingWindow extends JDialog {
|
|||||||
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
gbc.insets = new Insets(5, 5, 5, 5);
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
gbc.weightx = 1.0; // Add horizontal weight
|
gbc.weightx = 1.0;
|
||||||
gbc.weighty = 0.0; // Add vertical weight
|
gbc.weighty = 0.0;
|
||||||
|
|
||||||
// Add icon
|
// Add icon
|
||||||
try {
|
try {
|
||||||
@@ -43,12 +46,14 @@ public class LoadingWindow extends JDialog {
|
|||||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
gbc.gridy = 0;
|
gbc.gridy = 0;
|
||||||
mainPanel.add(iconLabel, gbc);
|
mainPanel.add(iconLabel, gbc);
|
||||||
|
log.debug("Icon loaded and scaled successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to load icon", e);
|
log.error("Failed to load icon", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL Label with explicit size
|
// URL Label with explicit size
|
||||||
brandLabel = new JLabel(initialUrl);
|
brandLabel = new JLabel(initialUrl);
|
||||||
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
@@ -63,6 +68,7 @@ public class LoadingWindow extends JDialog {
|
|||||||
statusLabel.setPreferredSize(new Dimension(300, 25));
|
statusLabel.setPreferredSize(new Dimension(300, 25));
|
||||||
gbc.gridy = 2;
|
gbc.gridy = 2;
|
||||||
mainPanel.add(statusLabel, gbc);
|
mainPanel.add(statusLabel, gbc);
|
||||||
|
|
||||||
// Progress bar with explicit size
|
// Progress bar with explicit size
|
||||||
progressBar = new JProgressBar(0, 100);
|
progressBar = new JProgressBar(0, 100);
|
||||||
progressBar.setStringPainted(true);
|
progressBar.setStringPainted(true);
|
||||||
@@ -82,33 +88,78 @@ public class LoadingWindow extends JDialog {
|
|||||||
setAlwaysOnTop(true);
|
setAlwaysOnTop(true);
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
setStatus("Starting...");
|
setStatus("Starting...");
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"LoadingWindow initialization completed in {}ms",
|
||||||
|
System.currentTimeMillis() - startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProgress(final int progress) {
|
public void setProgress(final int progress) {
|
||||||
SwingUtilities.invokeLater(
|
SwingUtilities.invokeLater(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
|
int validProgress = Math.min(Math.max(progress, 0), 100);
|
||||||
progressBar.setString(progress + "%");
|
log.info(
|
||||||
|
"Setting progress to {}% at {}ms since start",
|
||||||
|
validProgress, System.currentTimeMillis() - startTime);
|
||||||
|
|
||||||
|
// Log additional details when near 90%
|
||||||
|
if (validProgress >= 85 && validProgress <= 95) {
|
||||||
|
log.info(
|
||||||
|
"Near 90% progress - Current status: {}, Window visible: {}, "
|
||||||
|
+ "Progress bar responding: {}, Memory usage: {}MB",
|
||||||
|
statusLabel.getText(),
|
||||||
|
isVisible(),
|
||||||
|
progressBar.isEnabled(),
|
||||||
|
Runtime.getRuntime().totalMemory() / (1024 * 1024));
|
||||||
|
|
||||||
|
// Add thread state logging
|
||||||
|
Thread currentThread = Thread.currentThread();
|
||||||
|
log.debug(
|
||||||
|
"Current thread state - Name: {}, State: {}, Priority: {}",
|
||||||
|
currentThread.getName(),
|
||||||
|
currentThread.getState(),
|
||||||
|
currentThread.getPriority());
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.setValue(validProgress);
|
||||||
|
progressBar.setString(validProgress + "%");
|
||||||
mainPanel.revalidate();
|
mainPanel.revalidate();
|
||||||
mainPanel.repaint();
|
mainPanel.repaint();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error updating progress", e);
|
log.error("Error updating progress to " + progress, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatus(final String status) {
|
public void setStatus(final String status) {
|
||||||
log.info(status);
|
log.info(
|
||||||
|
"Status update at {}ms - Setting status to: {}",
|
||||||
|
System.currentTimeMillis() - startTime,
|
||||||
|
status);
|
||||||
|
|
||||||
SwingUtilities.invokeLater(
|
SwingUtilities.invokeLater(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
statusLabel.setText(status != null ? status : "");
|
String validStatus = status != null ? status : "";
|
||||||
|
statusLabel.setText(validStatus);
|
||||||
|
|
||||||
|
// Log UI state when status changes
|
||||||
|
log.debug(
|
||||||
|
"UI State - Window visible: {}, Progress: {}%, Status: {}",
|
||||||
|
isVisible(), progressBar.getValue(), validStatus);
|
||||||
|
|
||||||
mainPanel.revalidate();
|
mainPanel.revalidate();
|
||||||
mainPanel.repaint();
|
mainPanel.repaint();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error updating status", e);
|
log.error("Error updating status to: " + status, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
log.info("LoadingWindow disposing after {}ms", System.currentTimeMillis() - startTime);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,16 +136,6 @@ public class AppConfig {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "watchedFoldersDir")
|
|
||||||
public String watchedFoldersDir() {
|
|
||||||
return "./pipeline/watchedFolders/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "finishedFoldersDir")
|
|
||||||
public String finishedFoldersDir() {
|
|
||||||
return "./pipeline/finishedFolders/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "directoryFilter")
|
@Bean(name = "directoryFilter")
|
||||||
public Predicate<Path> processOnlyFiles() {
|
public Predicate<Path> processOnlyFiles() {
|
||||||
return path -> {
|
return path -> {
|
||||||
|
|||||||
@@ -16,27 +16,15 @@ import org.simpleyaml.configuration.comments.CommentType;
|
|||||||
import org.simpleyaml.configuration.file.YamlFile;
|
import org.simpleyaml.configuration.file.YamlFile;
|
||||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ConfigInitializer
|
public class ConfigInitializer {
|
||||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
|
||||||
try {
|
|
||||||
ensureConfigExists();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to initialize application configuration", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ensureConfigExists() throws IOException, URISyntaxException {
|
public void ensureConfigExists() throws IOException, URISyntaxException {
|
||||||
// Define the path to the external config directory
|
// Define the path to the external config directory
|
||||||
Path destPath = Paths.get("configs", "settings.yml");
|
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
|
|
||||||
// Check if the file already exists
|
// Check if the file already exists
|
||||||
if (Files.notExists(destPath)) {
|
if (Files.notExists(destPath)) {
|
||||||
@@ -53,10 +41,11 @@ public class ConfigInitializer
|
|||||||
"Resource file not found: settings.yml.template");
|
"Resource file not found: settings.yml.template");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.info("Created settings file from template");
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Define the path to the config settings file
|
// Define the path to the config settings file
|
||||||
Path settingsPath = Paths.get("configs", "settings.yml");
|
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
// Load the template resource
|
// Load the template resource
|
||||||
URL settingsTemplateResource =
|
URL settingsTemplateResource =
|
||||||
getClass().getClassLoader().getResource("settings.yml.template");
|
getClass().getClassLoader().getResource("settings.yml.template");
|
||||||
@@ -120,7 +109,7 @@ public class ConfigInitializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create custom settings file if it doesn't exist
|
// Create custom settings file if it doesn't exist
|
||||||
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
|
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||||
if (!Files.exists(customSettingsPath)) {
|
if (!Files.exists(customSettingsPath)) {
|
||||||
Files.createFile(customSettingsPath);
|
Files.createFile(customSettingsPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -135,6 +136,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Security", "remove-cert-sign");
|
addEndpointToGroup("Security", "remove-cert-sign");
|
||||||
addEndpointToGroup("Security", "sanitize-pdf");
|
addEndpointToGroup("Security", "sanitize-pdf");
|
||||||
addEndpointToGroup("Security", "auto-redact");
|
addEndpointToGroup("Security", "auto-redact");
|
||||||
|
addEndpointToGroup("Security", "redact");
|
||||||
|
|
||||||
// Adding endpoints to "Other" group
|
// Adding endpoints to "Other" group
|
||||||
addEndpointToGroup("Other", "ocr-pdf");
|
addEndpointToGroup("Other", "ocr-pdf");
|
||||||
@@ -180,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
|
||||||
@@ -234,6 +235,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Java", "markdown-to-pdf");
|
addEndpointToGroup("Java", "markdown-to-pdf");
|
||||||
addEndpointToGroup("Java", "show-javascript");
|
addEndpointToGroup("Java", "show-javascript");
|
||||||
addEndpointToGroup("Java", "auto-redact");
|
addEndpointToGroup("Java", "auto-redact");
|
||||||
|
addEndpointToGroup("Java", "redact");
|
||||||
addEndpointToGroup("Java", "pdf-to-csv");
|
addEndpointToGroup("Java", "pdf-to-csv");
|
||||||
addEndpointToGroup("Java", "split-by-size-or-count");
|
addEndpointToGroup("Java", "split-by-size-or-count");
|
||||||
addEndpointToGroup("Java", "overlay-pdf");
|
addEndpointToGroup("Java", "overlay-pdf");
|
||||||
@@ -265,20 +267,26 @@ public class EndpointConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
if (applicationProperties != null && applicationProperties.getEndpoints() != null) {
|
||||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||||
if (!bookAndHtmlFormatsInstalled) {
|
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||||
groupsToRemove.add("Calibre");
|
|
||||||
}
|
|
||||||
if (endpointsToRemove != null) {
|
|
||||||
for (String endpoint : endpointsToRemove) {
|
|
||||||
disableEndpoint(endpoint.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupsToRemove != null) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
for (String group : groupsToRemove) {
|
if (groupsToRemove == null) {
|
||||||
disableGroup(group.trim());
|
groupsToRemove = new ArrayList<>();
|
||||||
|
}
|
||||||
|
groupsToRemove.add("Calibre");
|
||||||
|
}
|
||||||
|
if (endpointsToRemove != null) {
|
||||||
|
for (String endpoint : endpointsToRemove) {
|
||||||
|
disableEndpoint(endpoint.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemove != null) {
|
||||||
|
for (String group : groupsToRemove) {
|
||||||
|
disableGroup(group.trim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
|
|||||||
String characterEncoding,
|
String characterEncoding,
|
||||||
Map<String, Object> templateResolutionAttributes) {
|
Map<String, Object> templateResolutionAttributes) {
|
||||||
Resource resource =
|
Resource resource =
|
||||||
resourceLoader.getResource("file:./customFiles/templates/" + resourceName);
|
resourceLoader.getResource(
|
||||||
|
"file:" + InstallationPathConfig.getTemplatesPath() + resourceName);
|
||||||
try {
|
try {
|
||||||
if (resource.exists() && resource.isReadable()) {
|
if (resource.exists() && resource.isReadable()) {
|
||||||
return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);
|
return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class InstallationPathConfig {
|
||||||
|
private static final String BASE_PATH;
|
||||||
|
|
||||||
|
// Root paths
|
||||||
|
private static final String LOG_PATH;
|
||||||
|
private static final String CONFIG_PATH;
|
||||||
|
private static final String PIPELINE_PATH;
|
||||||
|
private static final String CUSTOM_FILES_PATH;
|
||||||
|
private static final String CLIENT_WEBUI_PATH;
|
||||||
|
|
||||||
|
// Config paths
|
||||||
|
private static final String SETTINGS_PATH;
|
||||||
|
private static final String CUSTOM_SETTINGS_PATH;
|
||||||
|
|
||||||
|
// Pipeline paths
|
||||||
|
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
|
||||||
|
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
|
private static final String PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
|
||||||
|
// Custom file paths
|
||||||
|
private static final String STATIC_PATH;
|
||||||
|
private static final String TEMPLATES_PATH;
|
||||||
|
private static final String SIGNATURES_PATH;
|
||||||
|
|
||||||
|
static {
|
||||||
|
BASE_PATH = initializeBasePath();
|
||||||
|
|
||||||
|
// Initialize root paths
|
||||||
|
LOG_PATH = BASE_PATH + "logs" + File.separator;
|
||||||
|
CONFIG_PATH = BASE_PATH + "configs" + File.separator;
|
||||||
|
PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator;
|
||||||
|
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
|
||||||
|
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
|
||||||
|
|
||||||
|
// Initialize config paths
|
||||||
|
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
||||||
|
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
||||||
|
|
||||||
|
// Initialize pipeline paths
|
||||||
|
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + 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
|
||||||
|
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||||
|
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
|
||||||
|
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String initializeBasePath() {
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
if (os.contains("win")) {
|
||||||
|
return System.getenv("APPDATA") + File.separator + "Stirling-PDF" + File.separator;
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
return System.getProperty("user.home")
|
||||||
|
+ File.separator
|
||||||
|
+ "Library"
|
||||||
|
+ File.separator
|
||||||
|
+ "Application Support"
|
||||||
|
+ File.separator
|
||||||
|
+ "Stirling-PDF"
|
||||||
|
+ File.separator;
|
||||||
|
} else {
|
||||||
|
return System.getProperty("user.home")
|
||||||
|
+ File.separator
|
||||||
|
+ ".config"
|
||||||
|
+ File.separator
|
||||||
|
+ "Stirling-PDF"
|
||||||
|
+ File.separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "./";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPath() {
|
||||||
|
return BASE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLogPath() {
|
||||||
|
return LOG_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getConfigPath() {
|
||||||
|
return CONFIG_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelinePath() {
|
||||||
|
return PIPELINE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCustomFilesPath() {
|
||||||
|
return CUSTOM_FILES_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientWebUIPath() {
|
||||||
|
return CLIENT_WEBUI_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSettingsPath() {
|
||||||
|
return SETTINGS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCustomSettingsPath() {
|
||||||
|
return CUSTOM_SETTINGS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelineWatchedFoldersDir() {
|
||||||
|
return PIPELINE_WATCHED_FOLDERS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelineFinishedFoldersDir() {
|
||||||
|
return PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelineDefaultWebUIConfigsDir() {
|
||||||
|
return PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStaticPath() {
|
||||||
|
return STATIC_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTemplatesPath() {
|
||||||
|
return TEMPLATES_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSignaturesPath() {
|
||||||
|
return SIGNATURES_PATH;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import ch.qos.logback.core.PropertyDefinerBase;
|
||||||
|
|
||||||
|
public class LogbackPropertyLoader extends PropertyDefinerBase {
|
||||||
|
@Override
|
||||||
|
public String getPropertyValue() {
|
||||||
|
return InstallationPathConfig.getLogPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// Handler for external static resources
|
// Handler for external static resources
|
||||||
registry.addResourceHandler("/**")
|
registry.addResourceHandler("/**")
|
||||||
.addResourceLocations("file:customFiles/static/", "classpath:/static/");
|
.addResourceLocations(
|
||||||
|
"file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/");
|
||||||
// .setCachePeriod(0); // Optional: disable caching
|
// .setCachePeriod(0); // Optional: disable caching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public class YamlPropertySourceFactory implements PropertySourceFactory {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
||||||
factory.setResources(encodedResource.getResource());
|
factory.setResources(encodedResource.getResource());
|
||||||
|
|
||||||
Properties properties = factory.getObject();
|
Properties properties = factory.getObject();
|
||||||
|
|
||||||
return new PropertiesPropertySource(
|
return new PropertiesPropertySource(
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.interfaces;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
|
||||||
|
|
||||||
public interface DatabaseBackupInterface {
|
|
||||||
|
|
||||||
void exportDatabase() throws IOException;
|
|
||||||
|
|
||||||
boolean importDatabase();
|
|
||||||
|
|
||||||
boolean hasBackup();
|
|
||||||
|
|
||||||
List<FileInfo> getBackupList();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package stirling.software.SPDF.config.interfaces;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
|
public interface DatabaseInterface {
|
||||||
|
void exportDatabase() throws SQLException, UnsupportedProviderException;
|
||||||
|
|
||||||
|
void importDatabase();
|
||||||
|
|
||||||
|
boolean hasBackup();
|
||||||
|
|
||||||
|
List<FileInfo> getBackupList();
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
import stirling.software.SPDF.SPDFApplication;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@@ -110,7 +110,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
// Construct URLs required for SAML configuration
|
||||||
String serverUrl =
|
String serverUrl =
|
||||||
SPdfApplication.getStaticBaseUrl() + ":" + SPdfApplication.getStaticPort();
|
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
String relyingPartyIdentifier =
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
||||||
|
|||||||
@@ -1,49 +1,56 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.sql.SQLException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Component
|
||||||
public class InitialSecuritySetup {
|
public class InitialSecuritySetup {
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private final DatabaseBackupInterface databaseBackupHelper;
|
private final DatabaseInterface databaseService;
|
||||||
|
|
||||||
public InitialSecuritySetup(
|
public InitialSecuritySetup(
|
||||||
UserService userService,
|
UserService userService,
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
DatabaseBackupInterface databaseBackupHelper) {
|
DatabaseInterface databaseService) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.databaseBackupHelper = databaseBackupHelper;
|
this.databaseService = databaseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() throws IllegalArgumentException, IOException {
|
public void init() {
|
||||||
if (databaseBackupHelper.hasBackup() && !userService.hasUsers()) {
|
try {
|
||||||
databaseBackupHelper.importDatabase();
|
if (databaseService.hasBackup()) {
|
||||||
} else if (!userService.hasUsers()) {
|
databaseService.importDatabase();
|
||||||
initializeAdminUser();
|
}
|
||||||
} else {
|
|
||||||
databaseBackupHelper.exportDatabase();
|
if (!userService.hasUsers()) {
|
||||||
|
initializeAdminUser();
|
||||||
|
}
|
||||||
|
|
||||||
userService.migrateOauth2ToSSO();
|
userService.migrateOauth2ToSSO();
|
||||||
|
initializeInternalApiUser();
|
||||||
|
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
||||||
|
log.error("Failed to initialize security setup.", e);
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
initializeInternalApiUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAdminUser() throws IOException {
|
private void initializeAdminUser() throws SQLException, UnsupportedProviderException {
|
||||||
String initialUsername =
|
String initialUsername =
|
||||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||||
String initialPassword =
|
String initialPassword =
|
||||||
@@ -52,36 +59,34 @@ public class InitialSecuritySetup {
|
|||||||
&& !initialUsername.isEmpty()
|
&& !initialUsername.isEmpty()
|
||||||
&& initialPassword != null
|
&& initialPassword != null
|
||||||
&& !initialPassword.isEmpty()
|
&& !initialPassword.isEmpty()
|
||||||
&& !userService.findByUsernameIgnoreCase(initialUsername).isPresent()) {
|
&& userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {
|
||||||
try {
|
|
||||||
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
||||||
log.info("Admin user created: " + initialUsername);
|
log.info("Admin user created: {}", initialUsername);
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
log.error("Failed to initialize security setup", e);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
createDefaultAdminUser();
|
createDefaultAdminUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createDefaultAdminUser() throws IllegalArgumentException, IOException {
|
private void createDefaultAdminUser() throws SQLException, UnsupportedProviderException {
|
||||||
String defaultUsername = "admin";
|
String defaultUsername = "admin";
|
||||||
String defaultPassword = "stirling";
|
String defaultPassword = "stirling";
|
||||||
if (!userService.findByUsernameIgnoreCase(defaultUsername).isPresent()) {
|
|
||||||
|
if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {
|
||||||
userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);
|
userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);
|
||||||
log.info("Default admin user created: " + defaultUsername);
|
log.info("Default admin user created: {}", defaultUsername);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeInternalApiUser() throws IllegalArgumentException, IOException {
|
private void initializeInternalApiUser()
|
||||||
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
|
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
userService.saveUser(
|
userService.saveUser(
|
||||||
Role.INTERNAL_API_USER.getRoleId(),
|
Role.INTERNAL_API_USER.getRoleId(),
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
Role.INTERNAL_API_USER.getRoleId());
|
Role.INTERNAL_API_USER.getRoleId());
|
||||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
|
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -22,22 +22,31 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
public UserAuthenticationFilter(
|
public UserAuthenticationFilter(
|
||||||
|
@Lazy ApplicationProperties applicationProperties,
|
||||||
@Lazy UserService userService,
|
@Lazy UserService userService,
|
||||||
SessionPersistentRegistry sessionPersistentRegistry,
|
SessionPersistentRegistry sessionPersistentRegistry,
|
||||||
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
this.loginEnabledValue = loginEnabledValue;
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
@@ -121,33 +130,67 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Check if the authenticated user is disabled and invalidate their session if so
|
// Check if the authenticated user is disabled and invalidate their session if so
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
|
||||||
|
Security securityProp = applicationProperties.getSecurity();
|
||||||
|
LoginMethod loginMethod = LoginMethod.UNKNOWN;
|
||||||
|
|
||||||
|
boolean blockRegistration = false;
|
||||||
|
|
||||||
|
// Extract username and determine the login method
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
username = ((UserDetails) principal).getUsername();
|
username = ((UserDetails) principal).getUsername();
|
||||||
|
loginMethod = LoginMethod.USERDETAILS;
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
username = ((OAuth2User) principal).getName();
|
username = ((OAuth2User) principal).getName();
|
||||||
|
loginMethod = LoginMethod.OAUTH2USER;
|
||||||
|
OAUTH2 oAuth = securityProp.getOauth2();
|
||||||
|
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
|
loginMethod = LoginMethod.SAML2USER;
|
||||||
|
SAML2 saml2 = securityProp.getSaml2();
|
||||||
|
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
username = (String) principal;
|
username = (String) principal;
|
||||||
|
loginMethod = LoginMethod.STRINGUSER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve all active sessions for the user
|
||||||
List<SessionInformation> sessionsInformations =
|
List<SessionInformation> sessionsInformations =
|
||||||
sessionPersistentRegistry.getAllSessions(principal, false);
|
sessionPersistentRegistry.getAllSessions(principal, false);
|
||||||
|
|
||||||
|
// Check if the user exists, is disabled, or needs session invalidation
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
|
log.debug("Validating user: {}", username);
|
||||||
boolean isUserExists = userService.usernameExistsIgnoreCase(username);
|
boolean isUserExists = userService.usernameExistsIgnoreCase(username);
|
||||||
boolean isUserDisabled = userService.isUserDisabled(username);
|
boolean isUserDisabled = userService.isUserDisabled(username);
|
||||||
|
|
||||||
|
boolean notSsoLogin =
|
||||||
|
!loginMethod.equals(LoginMethod.OAUTH2USER)
|
||||||
|
&& !loginMethod.equals(LoginMethod.SAML2USER);
|
||||||
|
|
||||||
|
// Block user registration if not allowed by configuration
|
||||||
|
if (blockRegistration && !isUserExists) {
|
||||||
|
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire sessions and logout if the user does not exist or is disabled
|
||||||
if (!isUserExists || isUserDisabled) {
|
if (!isUserExists || isUserDisabled) {
|
||||||
|
log.info(
|
||||||
|
"Invalidating session for disabled or non-existent user: {}", username);
|
||||||
for (SessionInformation sessionsInformation : sessionsInformations) {
|
for (SessionInformation sessionsInformation : sessionsInformations) {
|
||||||
sessionsInformation.expireNow();
|
sessionsInformation.expireNow();
|
||||||
sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUserExists) {
|
// Redirect to logout if credentials are invalid
|
||||||
|
if (!isUserExists && notSsoLogin) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
|
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -161,6 +204,25 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum LoginMethod {
|
||||||
|
USERDETAILS("UserDetails"),
|
||||||
|
OAUTH2USER("OAuth2User"),
|
||||||
|
STRINGUSER("StringUser"),
|
||||||
|
UNKNOWN("Unknown"),
|
||||||
|
SAML2USER("Saml2User");
|
||||||
|
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
LoginMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -20,11 +21,12 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
private final SessionPersistentRegistry sessionRegistry;
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
private final DatabaseBackupInterface databaseBackupHelper;
|
private final DatabaseInterface databaseService;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@@ -52,14 +54,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
MessageSource messageSource,
|
MessageSource messageSource,
|
||||||
SessionPersistentRegistry sessionRegistry,
|
SessionPersistentRegistry sessionRegistry,
|
||||||
DatabaseBackupInterface databaseBackupHelper,
|
DatabaseInterface databaseService,
|
||||||
ApplicationProperties applicationProperties) {
|
ApplicationProperties applicationProperties) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.authorityRepository = authorityRepository;
|
this.authorityRepository = authorityRepository;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
this.sessionRegistry = sessionRegistry;
|
this.sessionRegistry = sessionRegistry;
|
||||||
this.databaseBackupHelper = databaseBackupHelper;
|
this.databaseService = databaseService;
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -163,12 +165,12 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, AuthenticationType authenticationType)
|
public void saveUser(String username, AuthenticationType authenticationType)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
saveUser(username, authenticationType, Role.USER.getRoleId());
|
saveUser(username, authenticationType, Role.USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||||
}
|
}
|
||||||
@@ -179,11 +181,11 @@ public class UserService implements UserServiceInterface {
|
|||||||
user.addAuthority(new Authority(role, user));
|
user.addAuthority(new Authority(role, user));
|
||||||
user.setAuthenticationType(authenticationType);
|
user.setAuthenticationType(authenticationType);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password)
|
public void saveUser(String username, String password)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||||
}
|
}
|
||||||
@@ -193,11 +195,11 @@ public class UserService implements UserServiceInterface {
|
|||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setAuthenticationType(AuthenticationType.WEB);
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password, String role, boolean firstLogin)
|
public void saveUser(String username, String password, String role, boolean firstLogin)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||||
}
|
}
|
||||||
@@ -209,11 +211,11 @@ public class UserService implements UserServiceInterface {
|
|||||||
user.setAuthenticationType(AuthenticationType.WEB);
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
user.setFirstLogin(firstLogin);
|
user.setFirstLogin(firstLogin);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password, String role)
|
public void saveUser(String username, String password, String role)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
saveUser(username, password, role, false);
|
saveUser(username, password, role, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +249,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateUserSettings(String username, Map<String, String> updates)
|
public void updateUserSettings(String username, Map<String, String> updates)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Optional<User> userOpt = findByUsernameIgnoreCaseWithSettings(username);
|
Optional<User> userOpt = findByUsernameIgnoreCaseWithSettings(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@@ -259,7 +261,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
settingsMap.putAll(updates);
|
settingsMap.putAll(updates);
|
||||||
user.setSettings(settingsMap);
|
user.setSettings(settingsMap);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,38 +282,45 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void changeUsername(User user, String newUsername)
|
public void changeUsername(User user, String newUsername)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException,
|
||||||
|
IOException,
|
||||||
|
SQLException,
|
||||||
|
UnsupportedProviderException {
|
||||||
if (!isUsernameValid(newUsername)) {
|
if (!isUsernameValid(newUsername)) {
|
||||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||||
}
|
}
|
||||||
user.setUsername(newUsername);
|
user.setUsername(newUsername);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changePassword(User user, String newPassword) throws IOException {
|
public void changePassword(User user, String newPassword)
|
||||||
|
throws SQLException, UnsupportedProviderException {
|
||||||
user.setPassword(passwordEncoder.encode(newPassword));
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeFirstUse(User user, boolean firstUse) throws IOException {
|
public void changeFirstUse(User user, boolean firstUse)
|
||||||
|
throws SQLException, UnsupportedProviderException {
|
||||||
user.setFirstLogin(firstUse);
|
user.setFirstLogin(firstUse);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeRole(User user, String newRole) throws IOException {
|
public void changeRole(User user, String newRole)
|
||||||
|
throws SQLException, UnsupportedProviderException {
|
||||||
Authority userAuthority = this.findRole(user);
|
Authority userAuthority = this.findRole(user);
|
||||||
userAuthority.setAuthority(newRole);
|
userAuthority.setAuthority(newRole);
|
||||||
authorityRepository.save(userAuthority);
|
authorityRepository.save(userAuthority);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeUserEnabled(User user, Boolean enbeled) throws IOException {
|
public void changeUserEnabled(User user, Boolean enbeled)
|
||||||
|
throws SQLException, UnsupportedProviderException {
|
||||||
user.setEnabled(enbeled);
|
user.setEnabled(enbeled);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPasswordCorrect(User user, String currentPassword) {
|
public boolean isPasswordCorrect(User user, String currentPassword) {
|
||||||
@@ -320,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());
|
||||||
@@ -397,7 +410,8 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void syncCustomApiUser(String customApiKey) throws IOException {
|
public void syncCustomApiUser(String customApiKey)
|
||||||
|
throws SQLException, UnsupportedProviderException {
|
||||||
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -414,14 +428,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
user.setApiKey(customApiKey);
|
user.setApiKey(customApiKey);
|
||||||
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
} else {
|
} else {
|
||||||
// Update API key if it has changed
|
// Update API key if it has changed
|
||||||
User user = existingUser.get();
|
User user = existingUser.get();
|
||||||
if (!customApiKey.equals(user.getApiKey())) {
|
if (!customApiKey.equals(user.getApiKey())) {
|
||||||
user.setApiKey(customApiKey);
|
user.setApiKey(customApiKey);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,232 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.database;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.sql.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Configuration
|
|
||||||
public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
|
||||||
|
|
||||||
@Value("${spring.datasource.url}")
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.username}")
|
|
||||||
private String databaseUsername;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.password}")
|
|
||||||
private String databasePassword;
|
|
||||||
|
|
||||||
private Path backupPath = Paths.get("configs/db/backup/");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasBackup() {
|
|
||||||
// Check if there is at least one backup
|
|
||||||
return !getBackupList().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FileInfo> getBackupList() {
|
|
||||||
// Check if the backup directory exists, and create it if it does not
|
|
||||||
ensureBackupDirectoryExists();
|
|
||||||
|
|
||||||
List<FileInfo> backupFiles = new ArrayList<>();
|
|
||||||
|
|
||||||
// Read the backup directory and filter for files with the prefix "backup_" and suffix
|
|
||||||
// ".sql"
|
|
||||||
try (DirectoryStream<Path> stream =
|
|
||||||
Files.newDirectoryStream(
|
|
||||||
backupPath,
|
|
||||||
path ->
|
|
||||||
path.getFileName().toString().startsWith("backup_")
|
|
||||||
&& path.getFileName().toString().endsWith(".sql"))) {
|
|
||||||
for (Path entry : stream) {
|
|
||||||
BasicFileAttributes attrs = Files.readAttributes(entry, BasicFileAttributes.class);
|
|
||||||
LocalDateTime modificationDate =
|
|
||||||
LocalDateTime.ofInstant(
|
|
||||||
attrs.lastModifiedTime().toInstant(), ZoneId.systemDefault());
|
|
||||||
LocalDateTime creationDate =
|
|
||||||
LocalDateTime.ofInstant(
|
|
||||||
attrs.creationTime().toInstant(), ZoneId.systemDefault());
|
|
||||||
long fileSize = attrs.size();
|
|
||||||
backupFiles.add(
|
|
||||||
new FileInfo(
|
|
||||||
entry.getFileName().toString(),
|
|
||||||
entry.toString(),
|
|
||||||
modificationDate,
|
|
||||||
fileSize,
|
|
||||||
creationDate));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error reading backup directory: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return backupFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Imports a database backup from the specified file.
|
|
||||||
public boolean importDatabaseFromUI(String fileName) throws IOException {
|
|
||||||
return this.importDatabaseFromUI(getBackupFilePath(fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Imports a database backup from the specified path.
|
|
||||||
public boolean importDatabaseFromUI(Path tempTemplatePath) throws IOException {
|
|
||||||
boolean success = executeDatabaseScript(tempTemplatePath);
|
|
||||||
if (success) {
|
|
||||||
LocalDateTime dateNow = LocalDateTime.now();
|
|
||||||
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
|
||||||
Path insertOutputFilePath =
|
|
||||||
this.getBackupFilePath("backup_user_" + dateNow.format(myFormatObj) + ".sql");
|
|
||||||
Files.copy(tempTemplatePath, insertOutputFilePath);
|
|
||||||
Files.deleteIfExists(tempTemplatePath);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean importDatabase() {
|
|
||||||
if (!this.hasBackup()) return false;
|
|
||||||
|
|
||||||
List<FileInfo> backupList = this.getBackupList();
|
|
||||||
backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed());
|
|
||||||
|
|
||||||
return executeDatabaseScript(Paths.get(backupList.get(0).getFilePath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exportDatabase() throws IOException {
|
|
||||||
// Check if the backup directory exists, and create it if it does not
|
|
||||||
ensureBackupDirectoryExists();
|
|
||||||
|
|
||||||
// Filter and delete old backups if there are more than 5
|
|
||||||
List<FileInfo> filteredBackupList =
|
|
||||||
this.getBackupList().stream()
|
|
||||||
.filter(backup -> !backup.getFileName().startsWith("backup_user_"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (filteredBackupList.size() > 5) {
|
|
||||||
filteredBackupList.sort(
|
|
||||||
Comparator.comparing(
|
|
||||||
p -> p.getFileName().substring(7, p.getFileName().length() - 4)));
|
|
||||||
Files.deleteIfExists(Paths.get(filteredBackupList.get(0).getFilePath()));
|
|
||||||
log.info("Deleted oldest backup: {}", filteredBackupList.get(0).getFileName());
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalDateTime dateNow = LocalDateTime.now();
|
|
||||||
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
|
||||||
Path insertOutputFilePath =
|
|
||||||
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
|
|
||||||
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
|
|
||||||
|
|
||||||
try (Connection conn =
|
|
||||||
DriverManager.getConnection(url, databaseUsername, databasePassword);
|
|
||||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
|
||||||
stmt.setString(1, insertOutputFilePath.toString());
|
|
||||||
stmt.execute();
|
|
||||||
log.info("Database export completed: {}", insertOutputFilePath);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
log.error("Error during database export: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves the H2 database version.
|
|
||||||
public String getH2Version() {
|
|
||||||
String version = "Unknown";
|
|
||||||
try (Connection conn =
|
|
||||||
DriverManager.getConnection(url, databaseUsername, databasePassword)) {
|
|
||||||
try (Statement stmt = conn.createStatement();
|
|
||||||
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
|
|
||||||
if (rs.next()) {
|
|
||||||
version = rs.getString("version");
|
|
||||||
log.info("H2 Database Version: {}", version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
log.error("Error retrieving H2 version: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletes a backup file.
|
|
||||||
public boolean deleteBackupFile(String fileName) throws IOException {
|
|
||||||
if (!isValidFileName(fileName)) {
|
|
||||||
log.error("Invalid file name: {}", fileName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Path filePath = this.getBackupFilePath(fileName);
|
|
||||||
if (Files.deleteIfExists(filePath)) {
|
|
||||||
log.info("Deleted backup file: {}", fileName);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log.error("File not found or could not be deleted: {}", fileName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the Path object for a given backup file name.
|
|
||||||
public Path getBackupFilePath(String fileName) {
|
|
||||||
Path filePath = Paths.get(backupPath.toString(), fileName).normalize();
|
|
||||||
if (!filePath.startsWith(backupPath)) {
|
|
||||||
throw new SecurityException("Path traversal detected");
|
|
||||||
}
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean executeDatabaseScript(Path scriptPath) {
|
|
||||||
String query = "RUNSCRIPT from ?;";
|
|
||||||
|
|
||||||
try (Connection conn =
|
|
||||||
DriverManager.getConnection(url, databaseUsername, databasePassword);
|
|
||||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
|
||||||
stmt.setString(1, scriptPath.toString());
|
|
||||||
stmt.execute();
|
|
||||||
log.info("Database import completed: {}", scriptPath);
|
|
||||||
return true;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
log.error("Error during database import: {}", e.getMessage(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureBackupDirectoryExists() {
|
|
||||||
if (Files.notExists(backupPath)) {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(backupPath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error creating directories: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidFileName(String fileName) {
|
|
||||||
// Check for invalid characters or sequences
|
|
||||||
return fileName != null
|
|
||||||
&& !fileName.contains("..")
|
|
||||||
&& !fileName.contains("/")
|
|
||||||
&& !fileName.contains("\\")
|
|
||||||
&& !fileName.contains(":")
|
|
||||||
&& !fileName.contains("*")
|
|
||||||
&& !fileName.contains("?")
|
|
||||||
&& !fileName.contains("\"")
|
|
||||||
&& !fileName.contains("<")
|
|
||||||
&& !fileName.contains(">")
|
|
||||||
&& !fileName.contains("|");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Configuration
|
||||||
|
public class DatabaseConfig {
|
||||||
|
|
||||||
|
public final String DATASOURCE_DEFAULT_URL;
|
||||||
|
|
||||||
|
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_USERNAME = "sa";
|
||||||
|
public static final String POSTGRES_DRIVER = "org.postgresql.Driver";
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final 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.runningEE = runningEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the <code>DataSource</code> for the connection to the DB. If <code>useDefault</code>
|
||||||
|
* is set to <code>true</code>, it will use the default H2 DB. If it is set to <code>false
|
||||||
|
* </code>, it will use the user's custom configuration set in the settings.yml.
|
||||||
|
*
|
||||||
|
* @return a <code>DataSource</code> using the configuration settings in the settings.yml
|
||||||
|
* @throws UnsupportedProviderException if the type of database selected is not supported
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@Qualifier("dataSource")
|
||||||
|
public DataSource dataSource() throws UnsupportedProviderException {
|
||||||
|
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
|
||||||
|
|
||||||
|
if (!runningEE) {
|
||||||
|
return useDefaultDataSource(dataSourceBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationProperties.System system = applicationProperties.getSystem();
|
||||||
|
ApplicationProperties.Datasource datasource = system.getDatasource();
|
||||||
|
|
||||||
|
if (!datasource.isEnableCustomDatabase()) {
|
||||||
|
return useDefaultDataSource(dataSourceBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Using custom database configuration");
|
||||||
|
|
||||||
|
if (!datasource.getCustomDatabaseUrl().isBlank()) {
|
||||||
|
if (datasource.getCustomDatabaseUrl().contains("postgresql")) {
|
||||||
|
dataSourceBuilder.driverClassName(POSTGRES_DRIVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSourceBuilder.url(datasource.getCustomDatabaseUrl());
|
||||||
|
} else {
|
||||||
|
dataSourceBuilder.driverClassName(getDriverClassName(datasource.getType()));
|
||||||
|
dataSourceBuilder.url(
|
||||||
|
generateCustomDataSourceUrl(
|
||||||
|
datasource.getType(),
|
||||||
|
datasource.getHostName(),
|
||||||
|
datasource.getPort(),
|
||||||
|
datasource.getName()));
|
||||||
|
}
|
||||||
|
dataSourceBuilder.username(datasource.getUsername());
|
||||||
|
dataSourceBuilder.password(datasource.getPassword());
|
||||||
|
|
||||||
|
return dataSourceBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource useDefaultDataSource(DataSourceBuilder<?> dataSourceBuilder) {
|
||||||
|
log.info("Using default H2 database");
|
||||||
|
|
||||||
|
dataSourceBuilder.url(DATASOURCE_DEFAULT_URL);
|
||||||
|
dataSourceBuilder.username(DEFAULT_USERNAME);
|
||||||
|
|
||||||
|
return dataSourceBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the URL the <code>DataSource</code> will use to connect to the database
|
||||||
|
*
|
||||||
|
* @param dataSourceType the type of the database
|
||||||
|
* @param hostname the host name
|
||||||
|
* @param port the port number to use for the database
|
||||||
|
* @param dataSourceName the name the database to connect to
|
||||||
|
* @return the <code>DataSource</code> URL
|
||||||
|
*/
|
||||||
|
private String generateCustomDataSourceUrl(
|
||||||
|
String dataSourceType, String hostname, Integer port, String dataSourceName) {
|
||||||
|
return DATASOURCE_URL_TEMPLATE.formatted(dataSourceType, hostname, port, dataSourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the database driver based on the type of database chosen.
|
||||||
|
*
|
||||||
|
* @param driverName the type of the driver (e.g. 'h2', 'postgresql')
|
||||||
|
* @return the fully qualified driver for the database chosen
|
||||||
|
* @throws UnsupportedProviderException when an unsupported database is selected
|
||||||
|
*/
|
||||||
|
private String getDriverClassName(String driverName) throws UnsupportedProviderException {
|
||||||
|
try {
|
||||||
|
ApplicationProperties.Driver driver =
|
||||||
|
ApplicationProperties.Driver.valueOf(driverName.toUpperCase());
|
||||||
|
|
||||||
|
switch (driver) {
|
||||||
|
case H2 -> {
|
||||||
|
log.debug("H2 driver selected");
|
||||||
|
return DEFAULT_DRIVER;
|
||||||
|
}
|
||||||
|
case POSTGRESQL -> {
|
||||||
|
log.debug("Postgres driver selected");
|
||||||
|
return POSTGRES_DRIVER;
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
log.warn("{} driver selected", driverName);
|
||||||
|
throw new UnsupportedProviderException(
|
||||||
|
driverName + " is not currently supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Unknown driver: {}", driverName);
|
||||||
|
throw new UnsupportedProviderException(driverName + " is not currently supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
|
||||||
|
import org.springframework.jdbc.datasource.init.ScriptException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.exception.BackupNotFoundException;
|
||||||
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class DatabaseService implements DatabaseInterface {
|
||||||
|
|
||||||
|
public static final String BACKUP_PREFIX = "backup_";
|
||||||
|
public static final String SQL_SUFFIX = ".sql";
|
||||||
|
private final Path BACKUP_DIR;
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
|
public DatabaseService(ApplicationProperties applicationProperties, DataSource dataSource) {
|
||||||
|
this.BACKUP_DIR =
|
||||||
|
Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there is at least one backup. First checks if the directory exists, then checks if
|
||||||
|
* there are backup scripts within the directory
|
||||||
|
*
|
||||||
|
* @return true if there are backup scripts, false if there are not
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasBackup() {
|
||||||
|
createBackupDirectory();
|
||||||
|
|
||||||
|
if (Files.exists(BACKUP_DIR)) {
|
||||||
|
return !getBackupList().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the backup directory and filter for files with the prefix "backup_" and suffix ".sql"
|
||||||
|
*
|
||||||
|
* @return a <code>List</code> of backup files
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<FileInfo> getBackupList() {
|
||||||
|
List<FileInfo> backupFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
if (isH2Database()) {
|
||||||
|
createBackupDirectory();
|
||||||
|
|
||||||
|
try (DirectoryStream<Path> stream =
|
||||||
|
Files.newDirectoryStream(
|
||||||
|
BACKUP_DIR,
|
||||||
|
path ->
|
||||||
|
path.getFileName().toString().startsWith(BACKUP_PREFIX)
|
||||||
|
&& path.getFileName()
|
||||||
|
.toString()
|
||||||
|
.endsWith(SQL_SUFFIX))) {
|
||||||
|
for (Path entry : stream) {
|
||||||
|
BasicFileAttributes attrs =
|
||||||
|
Files.readAttributes(entry, BasicFileAttributes.class);
|
||||||
|
LocalDateTime modificationDate =
|
||||||
|
LocalDateTime.ofInstant(
|
||||||
|
attrs.lastModifiedTime().toInstant(), ZoneId.systemDefault());
|
||||||
|
LocalDateTime creationDate =
|
||||||
|
LocalDateTime.ofInstant(
|
||||||
|
attrs.creationTime().toInstant(), ZoneId.systemDefault());
|
||||||
|
long fileSize = attrs.size();
|
||||||
|
backupFiles.add(
|
||||||
|
new FileInfo(
|
||||||
|
entry.getFileName().toString(),
|
||||||
|
entry.toString(),
|
||||||
|
modificationDate,
|
||||||
|
fileSize,
|
||||||
|
creationDate));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error reading backup directory: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public void importDatabase() {
|
||||||
|
if (!hasBackup()) throw new BackupNotFoundException("No backup scripts were found.");
|
||||||
|
|
||||||
|
List<FileInfo> backupList = this.getBackupList();
|
||||||
|
backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed());
|
||||||
|
|
||||||
|
Path latestExport = Paths.get(backupList.get(0).getFilePath());
|
||||||
|
|
||||||
|
executeDatabaseScript(latestExport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Imports a database backup from the specified file. */
|
||||||
|
public boolean importDatabaseFromUI(String fileName) {
|
||||||
|
try {
|
||||||
|
importDatabaseFromUI(getBackupFilePath(fileName));
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(
|
||||||
|
"Error importing database from file: {}, message: {}",
|
||||||
|
fileName,
|
||||||
|
e.getMessage(),
|
||||||
|
e.getCause());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Imports a database backup from the specified path. */
|
||||||
|
public boolean importDatabaseFromUI(Path tempTemplatePath) throws IOException {
|
||||||
|
executeDatabaseScript(tempTemplatePath);
|
||||||
|
LocalDateTime dateNow = LocalDateTime.now();
|
||||||
|
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||||
|
Path insertOutputFilePath =
|
||||||
|
this.getBackupFilePath(
|
||||||
|
BACKUP_PREFIX + "user_" + dateNow.format(myFormatObj) + SQL_SUFFIX);
|
||||||
|
Files.copy(tempTemplatePath, insertOutputFilePath);
|
||||||
|
Files.deleteIfExists(tempTemplatePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportDatabase() {
|
||||||
|
List<FileInfo> filteredBackupList =
|
||||||
|
this.getBackupList().stream()
|
||||||
|
.filter(backup -> !backup.getFileName().startsWith(BACKUP_PREFIX + "user_"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (filteredBackupList.size() > 5) {
|
||||||
|
deleteOldestBackup(filteredBackupList);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime dateNow = LocalDateTime.now();
|
||||||
|
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||||
|
Path insertOutputFilePath =
|
||||||
|
this.getBackupFilePath(BACKUP_PREFIX + dateNow.format(myFormatObj) + SQL_SUFFIX);
|
||||||
|
|
||||||
|
if (isH2Database()) {
|
||||||
|
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
|
||||||
|
|
||||||
|
try (Connection conn = dataSource.getConnection();
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
|
stmt.setString(1, insertOutputFilePath.toString());
|
||||||
|
stmt.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error during database export: {}", e.getMessage(), e);
|
||||||
|
} catch (CannotReadScriptException e) {
|
||||||
|
log.error("Error during database export: File {} not found", insertOutputFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Database export completed: {}", insertOutputFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteOldestBackup(List<FileInfo> filteredBackupList) {
|
||||||
|
try {
|
||||||
|
filteredBackupList.sort(
|
||||||
|
Comparator.comparing(
|
||||||
|
p -> p.getFileName().substring(7, p.getFileName().length() - 4)));
|
||||||
|
|
||||||
|
FileInfo oldestFile = filteredBackupList.get(0);
|
||||||
|
Files.deleteIfExists(Paths.get(oldestFile.getFilePath()));
|
||||||
|
log.info("Deleted oldest backup: {}", oldestFile.getFileName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Unable to delete oldest backup, message: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the H2 database version.
|
||||||
|
*
|
||||||
|
* @return <code>String</code> of the H2 version
|
||||||
|
*/
|
||||||
|
public String getH2Version() {
|
||||||
|
String version = "Unknown";
|
||||||
|
|
||||||
|
if (isH2Database()) {
|
||||||
|
try (Connection conn = dataSource.getConnection()) {
|
||||||
|
try (Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
|
||||||
|
if (rs.next()) {
|
||||||
|
version = rs.getString("version");
|
||||||
|
log.info("H2 Database Version: {}", version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error retrieving H2 version: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isH2Database() {
|
||||||
|
ApplicationProperties.Datasource datasource =
|
||||||
|
applicationProperties.getSystem().getDatasource();
|
||||||
|
return !datasource.isEnableCustomDatabase()
|
||||||
|
|| datasource.getType().equalsIgnoreCase(ApplicationProperties.Driver.H2.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a backup file.
|
||||||
|
*
|
||||||
|
* @return true if successful, false if not
|
||||||
|
*/
|
||||||
|
public boolean deleteBackupFile(String fileName) throws IOException {
|
||||||
|
if (!isValidFileName(fileName)) {
|
||||||
|
log.error("Invalid file name: {}", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Path filePath = this.getBackupFilePath(fileName);
|
||||||
|
if (Files.deleteIfExists(filePath)) {
|
||||||
|
log.info("Deleted backup file: {}", fileName);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.error("File not found or could not be deleted: {}", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Path for a given backup file name.
|
||||||
|
*
|
||||||
|
* @return the <code>Path</code> object for the given file name
|
||||||
|
*/
|
||||||
|
public Path getBackupFilePath(String fileName) {
|
||||||
|
createBackupDirectory();
|
||||||
|
Path filePath = BACKUP_DIR.resolve(fileName).normalize();
|
||||||
|
if (!filePath.startsWith(BACKUP_DIR)) {
|
||||||
|
throw new SecurityException("Path traversal detected");
|
||||||
|
}
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeDatabaseScript(Path scriptPath) {
|
||||||
|
if (isH2Database()) {
|
||||||
|
String query = "RUNSCRIPT from ?;";
|
||||||
|
|
||||||
|
try (Connection conn = dataSource.getConnection();
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
|
stmt.setString(1, scriptPath.toString());
|
||||||
|
stmt.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error during database import: {}", e.getMessage(), e);
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
log.error("Error: File {} not found", scriptPath.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Database import completed: {}", scriptPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for invalid characters or sequences
|
||||||
|
*
|
||||||
|
* @return true if it contains no invalid characters, false if it does
|
||||||
|
*/
|
||||||
|
private boolean isValidFileName(String fileName) {
|
||||||
|
return fileName != null
|
||||||
|
&& !fileName.contains("..")
|
||||||
|
&& !fileName.contains("/")
|
||||||
|
&& !fileName.contains("\\")
|
||||||
|
&& !fileName.contains(":")
|
||||||
|
&& !fileName.contains("*")
|
||||||
|
&& !fileName.contains("?")
|
||||||
|
&& !fileName.contains("\"")
|
||||||
|
&& !fileName.contains("<")
|
||||||
|
&& !fileName.contains(">")
|
||||||
|
&& !fileName.contains("|");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,27 @@
|
|||||||
package stirling.software.SPDF.config.security.database;
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
|
import stirling.software.SPDF.controller.api.H2SQLCondition;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Conditional(H2SQLCondition.class)
|
||||||
public class ScheduledTasks {
|
public class ScheduledTasks {
|
||||||
|
|
||||||
private final DatabaseBackupHelper databaseBackupService;
|
private final DatabaseInterface databaseService;
|
||||||
|
|
||||||
public ScheduledTasks(DatabaseBackupHelper databaseBackupService) {
|
public ScheduledTasks(DatabaseInterface databaseService) {
|
||||||
this.databaseBackupService = databaseBackupService;
|
this.databaseService = databaseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0 0 * * ?")
|
@Scheduled(cron = "0 0 0 * * ?")
|
||||||
public void performBackup() throws IOException {
|
public void performBackup() throws SQLException, UnsupportedProviderException {
|
||||||
databaseBackupService.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security.oauth2;
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -18,6 +19,7 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
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;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
public class CustomOAuth2AuthenticationSuccessHandler
|
public class CustomOAuth2AuthenticationSuccessHandler
|
||||||
@@ -97,10 +99,8 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
|
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
|
||||||
}
|
}
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
return;
|
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
package stirling.software.SPDF.config.security.saml2;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -18,6 +19,7 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -109,7 +111,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
log.debug("Successfully processed authentication for user: {}", username);
|
log.debug("Successfully processed authentication for user: {}", username);
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
return;
|
return;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Invalid username detected for user: {}, redirecting to logout",
|
"Invalid username detected for user: {}, redirecting to logout",
|
||||||
username);
|
username);
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import org.springframework.core.convert.converter.Converter;
|
|||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomSaml2ResponseAuthenticationConverter
|
public class CustomSaml2ResponseAuthenticationConverter
|
||||||
implements Converter<ResponseToken, Saml2Authentication> {
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -24,19 +25,20 @@ import io.swagger.v3.oas.annotations.Parameter;
|
|||||||
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.config.security.database.DatabaseBackupHelper;
|
import stirling.software.SPDF.config.security.database.DatabaseService;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/api/v1/database")
|
@RequestMapping("/api/v1/database")
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@Conditional(H2SQLCondition.class)
|
||||||
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
|
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
|
||||||
public class DatabaseController {
|
public class DatabaseController {
|
||||||
|
|
||||||
private final DatabaseBackupHelper databaseBackupHelper;
|
private final DatabaseService databaseService;
|
||||||
|
|
||||||
public DatabaseController(DatabaseBackupHelper databaseBackupHelper) {
|
public DatabaseController(DatabaseService databaseService) {
|
||||||
this.databaseBackupHelper = databaseBackupHelper;
|
this.databaseService = databaseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -57,7 +59,7 @@ public class DatabaseController {
|
|||||||
Path tempTemplatePath = Files.createTempFile("backup_", ".sql");
|
Path tempTemplatePath = Files.createTempFile("backup_", ".sql");
|
||||||
try (InputStream in = file.getInputStream()) {
|
try (InputStream in = file.getInputStream()) {
|
||||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
boolean importSuccess = databaseBackupHelper.importDatabaseFromUI(tempTemplatePath);
|
boolean importSuccess = databaseService.importDatabaseFromUI(tempTemplatePath);
|
||||||
if (importSuccess) {
|
if (importSuccess) {
|
||||||
redirectAttributes.addAttribute("infoMessage", "importIntoDatabaseSuccessed");
|
redirectAttributes.addAttribute("infoMessage", "importIntoDatabaseSuccessed");
|
||||||
} else {
|
} else {
|
||||||
@@ -77,21 +79,20 @@ public class DatabaseController {
|
|||||||
@GetMapping("/import-database-file/{fileName}")
|
@GetMapping("/import-database-file/{fileName}")
|
||||||
public String importDatabaseFromBackupUI(
|
public String importDatabaseFromBackupUI(
|
||||||
@Parameter(description = "Name of the file to import", required = true) @PathVariable
|
@Parameter(description = "Name of the file to import", required = true) @PathVariable
|
||||||
String fileName)
|
String fileName) {
|
||||||
throws IOException {
|
|
||||||
if (fileName == null || fileName.isEmpty()) {
|
if (fileName == null || fileName.isEmpty()) {
|
||||||
return "redirect:/database?error=fileNullOrEmpty";
|
return "redirect:/database?error=fileNullOrEmpty";
|
||||||
}
|
}
|
||||||
// Check if the file exists in the backup list
|
// Check if the file exists in the backup list
|
||||||
boolean fileExists =
|
boolean fileExists =
|
||||||
databaseBackupHelper.getBackupList().stream()
|
databaseService.getBackupList().stream()
|
||||||
.anyMatch(backup -> backup.getFileName().equals(fileName));
|
.anyMatch(backup -> backup.getFileName().equals(fileName));
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
log.error("File {} not found in backup list", fileName);
|
log.error("File {} not found in backup list", fileName);
|
||||||
return "redirect:/database?error=fileNotFound";
|
return "redirect:/database?error=fileNotFound";
|
||||||
}
|
}
|
||||||
log.info("Received file: {}", fileName);
|
log.info("Received file: {}", fileName);
|
||||||
if (databaseBackupHelper.importDatabaseFromUI(fileName)) {
|
if (databaseService.importDatabaseFromUI(fileName)) {
|
||||||
log.info("File {} imported to database", fileName);
|
log.info("File {} imported to database", fileName);
|
||||||
return "redirect:/database?infoMessage=importIntoDatabaseSuccessed";
|
return "redirect:/database?infoMessage=importIntoDatabaseSuccessed";
|
||||||
}
|
}
|
||||||
@@ -110,7 +111,7 @@ public class DatabaseController {
|
|||||||
throw new IllegalArgumentException("File must not be null or empty");
|
throw new IllegalArgumentException("File must not be null or empty");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (databaseBackupHelper.deleteBackupFile(fileName)) {
|
if (databaseService.deleteBackupFile(fileName)) {
|
||||||
log.info("Deleted file: {}", fileName);
|
log.info("Deleted file: {}", fileName);
|
||||||
} else {
|
} else {
|
||||||
log.error("Failed to delete file: {}", fileName);
|
log.error("Failed to delete file: {}", fileName);
|
||||||
@@ -135,7 +136,7 @@ public class DatabaseController {
|
|||||||
throw new IllegalArgumentException("File must not be null or empty");
|
throw new IllegalArgumentException("File must not be null or empty");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Path filePath = databaseBackupHelper.getBackupFilePath(fileName);
|
Path filePath = databaseService.getBackupFilePath(fileName);
|
||||||
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
|
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
|
||||||
@@ -157,14 +158,9 @@ public class DatabaseController {
|
|||||||
+ " database management page.")
|
+ " database management page.")
|
||||||
@GetMapping("/createDatabaseBackup")
|
@GetMapping("/createDatabaseBackup")
|
||||||
public String createDatabaseBackup() {
|
public String createDatabaseBackup() {
|
||||||
try {
|
log.info("Starting database backup creation...");
|
||||||
log.info("Starting database backup creation...");
|
databaseService.exportDatabase();
|
||||||
databaseBackupHelper.exportDatabase();
|
log.info("Database backup successfully created.");
|
||||||
log.info("Database backup successfully created.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error creating database backup: {}", e.getMessage(), e);
|
|
||||||
return "redirect:/database?error=" + e.getMessage();
|
|
||||||
}
|
|
||||||
return "redirect:/database?infoMessage=backupCreated";
|
return "redirect:/database?infoMessage=backupCreated";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Condition;
|
||||||
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
|
||||||
|
public class H2SQLCondition implements Condition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||||
|
boolean enableCustomDatabase =
|
||||||
|
Boolean.parseBoolean(
|
||||||
|
context.getEnvironment()
|
||||||
|
.getProperty("system.datasource.enableCustomDatabase"));
|
||||||
|
String dataSourceType = context.getEnvironment().getProperty("system.datasource.type");
|
||||||
|
return !enableCustomDatabase
|
||||||
|
|| (enableCustomDatabase && "h2".equalsIgnoreCase(dataSourceType));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ public class SettingsController {
|
|||||||
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||||
.body(
|
.body(
|
||||||
"Setting has already been set, To adjust please edit /config/settings.yml");
|
"Setting has already been set, To adjust please edit "
|
||||||
|
+ InstallationPathConfig.getSettingsPath());
|
||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -33,6 +34,7 @@ import stirling.software.SPDF.model.AuthenticationType;
|
|||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "User", description = "User APIs")
|
@Tag(name = "User", description = "User APIs")
|
||||||
@@ -52,7 +54,7 @@ public class UserController {
|
|||||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
|
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {
|
if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {
|
||||||
model.addAttribute("error", "Username already exists");
|
model.addAttribute("error", "Username already exists");
|
||||||
return "register";
|
return "register";
|
||||||
@@ -74,7 +76,7 @@ public class UserController {
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
RedirectAttributes redirectAttributes)
|
RedirectAttributes redirectAttributes)
|
||||||
throws IOException {
|
throws IOException, SQLException, UnsupportedProviderException {
|
||||||
if (!userService.isUsernameValid(newUsername)) {
|
if (!userService.isUsernameValid(newUsername)) {
|
||||||
return new RedirectView("/account?messageType=invalidUsername", true);
|
return new RedirectView("/account?messageType=invalidUsername", true);
|
||||||
}
|
}
|
||||||
@@ -117,7 +119,7 @@ public class UserController {
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
RedirectAttributes redirectAttributes)
|
RedirectAttributes redirectAttributes)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
@@ -145,7 +147,7 @@ public class UserController {
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
RedirectAttributes redirectAttributes)
|
RedirectAttributes redirectAttributes)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
@@ -166,7 +168,7 @@ public class UserController {
|
|||||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/updateUserSettings")
|
@PostMapping("/updateUserSettings")
|
||||||
public String updateUserSettings(HttpServletRequest request, Principal principal)
|
public String updateUserSettings(HttpServletRequest request, Principal principal)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Map<String, String[]> paramMap = request.getParameterMap();
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
Map<String, String> updates = new HashMap<>();
|
Map<String, String> updates = new HashMap<>();
|
||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
@@ -188,7 +190,7 @@ public class UserController {
|
|||||||
@RequestParam(name = "authType") String authType,
|
@RequestParam(name = "authType") String authType,
|
||||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||||
boolean forceChange)
|
boolean forceChange)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!userService.isUsernameValid(username)) {
|
if (!userService.isUsernameValid(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
||||||
}
|
}
|
||||||
@@ -232,7 +234,7 @@ public class UserController {
|
|||||||
@RequestParam(name = "username") String username,
|
@RequestParam(name = "username") String username,
|
||||||
@RequestParam(name = "role") String role,
|
@RequestParam(name = "role") String role,
|
||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (!userOpt.isPresent()) {
|
if (!userOpt.isPresent()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
@@ -270,7 +272,7 @@ public class UserController {
|
|||||||
@PathVariable("username") String username,
|
@PathVariable("username") String username,
|
||||||
@RequestParam("enabled") boolean enabled,
|
@RequestParam("enabled") boolean enabled,
|
||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws IOException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (!userOpt.isPresent()) {
|
if (!userOpt.isPresent()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import java.util.zip.ZipEntry;
|
|||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -31,11 +34,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.CheckProgramInstall;
|
import stirling.software.SPDF.utils.*;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
@@ -62,14 +62,20 @@ public class ConvertImgPDFController {
|
|||||||
String singleOrMultiple = request.getSingleOrMultiple();
|
String singleOrMultiple = request.getSingleOrMultiple();
|
||||||
String colorType = request.getColorType();
|
String colorType = request.getColorType();
|
||||||
String dpi = request.getDpi();
|
String dpi = request.getDpi();
|
||||||
|
String pageNumbers = request.getPageNumbers();
|
||||||
Path tempFile = null;
|
Path tempFile = null;
|
||||||
Path tempOutputDir = null;
|
Path tempOutputDir = null;
|
||||||
Path tempPdfPath = null;
|
Path tempPdfPath = null;
|
||||||
byte[] result = null;
|
byte[] result = null;
|
||||||
|
String[] pageOrderArr =
|
||||||
|
(pageNumbers != null && !pageNumbers.trim().isEmpty())
|
||||||
|
? pageNumbers.split(",")
|
||||||
|
: new String[] {"all"};
|
||||||
|
;
|
||||||
try {
|
try {
|
||||||
byte[] pdfBytes = file.getBytes();
|
// Load the input PDF
|
||||||
|
byte[] newPdfBytes = rearrangePdfPages(file.getBytes(), pageOrderArr);
|
||||||
|
|
||||||
ImageType colorTypeResult = ImageType.RGB;
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
if ("greyscale".equals(colorType)) {
|
if ("greyscale".equals(colorType)) {
|
||||||
colorTypeResult = ImageType.GRAY;
|
colorTypeResult = ImageType.GRAY;
|
||||||
@@ -84,7 +90,7 @@ public class ConvertImgPDFController {
|
|||||||
|
|
||||||
result =
|
result =
|
||||||
PdfUtils.convertFromPdf(
|
PdfUtils.convertFromPdf(
|
||||||
pdfBytes,
|
newPdfBytes,
|
||||||
"webp".equalsIgnoreCase(imageFormat)
|
"webp".equalsIgnoreCase(imageFormat)
|
||||||
? "png"
|
? "png"
|
||||||
: imageFormat.toUpperCase(),
|
: imageFormat.toUpperCase(),
|
||||||
@@ -227,4 +233,46 @@ public class ConvertImgPDFController {
|
|||||||
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
|
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
|
||||||
return "null".equals(mimeType) ? "application/octet-stream" : mimeType;
|
return "null".equals(mimeType) ? "application/octet-stream" : mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rearranges the pages of the given PDF document based on the specified page order.
|
||||||
|
*
|
||||||
|
* @param pdfBytes The byte array of the original PDF file.
|
||||||
|
* @param pageOrderArr An array of page numbers indicating the new order.
|
||||||
|
* @return A byte array of the rearranged PDF.
|
||||||
|
* @throws IOException If an error occurs while processing the PDF.
|
||||||
|
*/
|
||||||
|
private byte[] rearrangePdfPages(byte[] pdfBytes, String[] pageOrderArr) throws IOException {
|
||||||
|
// Load the input PDF
|
||||||
|
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||||
|
int totalPages = document.getNumberOfPages();
|
||||||
|
List<Integer> newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
||||||
|
|
||||||
|
// Create a new list to hold the pages in the new order
|
||||||
|
List<PDPage> newPages = new ArrayList<>();
|
||||||
|
for (int pageIndex : newPageOrder) {
|
||||||
|
newPages.add(document.getPage(pageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all the pages from the original document
|
||||||
|
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
||||||
|
document.removePage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the pages in the new order
|
||||||
|
for (PDPage page : newPages) {
|
||||||
|
document.addPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert PDDocument to byte array
|
||||||
|
byte[] newPdfBytes;
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
|
document.save(baos);
|
||||||
|
newPdfBytes = baos.toByteArray();
|
||||||
|
} finally {
|
||||||
|
document.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPdfBytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
import stirling.software.SPDF.SPDFApplication;
|
||||||
import stirling.software.SPDF.model.ApiEndpoint;
|
import stirling.software.SPDF.model.ApiEndpoint;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ public class ApiDocService {
|
|||||||
|
|
||||||
private String getApiDocsUrl() {
|
private String getApiDocsUrl() {
|
||||||
String contextPath = servletContext.getContextPath();
|
String contextPath = servletContext.getContextPath();
|
||||||
String port = SPdfApplication.getStaticPort();
|
String port = SPDFApplication.getStaticPort();
|
||||||
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
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.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@@ -35,22 +34,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
||||||
public class PipelineController {
|
public class PipelineController {
|
||||||
|
|
||||||
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
|
||||||
|
|
||||||
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
|
||||||
|
|
||||||
private final PipelineProcessor processor;
|
private final PipelineProcessor processor;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public PipelineController(
|
public PipelineController(PipelineProcessor processor, ObjectMapper objectMapper) {
|
||||||
PipelineProcessor processor,
|
|
||||||
ApplicationProperties applicationProperties,
|
|
||||||
ObjectMapper objectMapper) {
|
|
||||||
this.processor = processor;
|
this.processor = processor;
|
||||||
this.applicationProperties = applicationProperties;
|
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
@@ -25,6 +24,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
import stirling.software.SPDF.utils.FileMonitor;
|
import stirling.software.SPDF.utils.FileMonitor;
|
||||||
@@ -48,14 +48,12 @@ public class PipelineDirectoryProcessor {
|
|||||||
public PipelineDirectoryProcessor(
|
public PipelineDirectoryProcessor(
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
ApiDocService apiDocService,
|
ApiDocService apiDocService,
|
||||||
@Qualifier("watchedFoldersDir") String watchedFoldersDir,
|
|
||||||
@Qualifier("finishedFoldersDir") String finishedFoldersDir,
|
|
||||||
PipelineProcessor processor,
|
PipelineProcessor processor,
|
||||||
FileMonitor fileMonitor) {
|
FileMonitor fileMonitor) {
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.apiDocService = apiDocService;
|
this.apiDocService = apiDocService;
|
||||||
this.watchedFoldersDir = watchedFoldersDir;
|
this.watchedFoldersDir = InstallationPathConfig.getPipelineWatchedFoldersDir();
|
||||||
this.finishedFoldersDir = finishedFoldersDir;
|
this.finishedFoldersDir = InstallationPathConfig.getPipelineFinishedFoldersDir();
|
||||||
this.processor = processor;
|
this.processor = processor;
|
||||||
this.fileMonitor = fileMonitor;
|
this.fileMonitor = fileMonitor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import io.github.pixee.security.ZipSecurity;
|
|||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
import stirling.software.SPDF.SPDFApplication;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
@@ -80,7 +80,7 @@ public class PipelineProcessor {
|
|||||||
|
|
||||||
private String getBaseUrl() {
|
private String getBaseUrl() {
|
||||||
String contextPath = servletContext.getContextPath();
|
String contextPath = servletContext.getContextPath();
|
||||||
String port = SPdfApplication.getStaticPort();
|
String port = SPDFApplication.getStaticPort();
|
||||||
return "http://localhost:" + port + contextPath + "/";
|
return "http://localhost:" + port + contextPath + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user