Compare commits

..

16 Commits

Author SHA1 Message Date
a
f7add727a6 Merge branch 'filePath' of git@github.com:Stirling-Tools/Stirling-PDF.git into filePath 2025-01-06 12:38:43 +00:00
Anthony Stirling
cd947c2554 formatting 2025-01-06 12:38:13 +00:00
Anthony Stirling
9422a30ebf Merge branch 'main' into filePath 2025-01-04 18:11:39 +00:00
Anthony Stirling
ed273a6e92 space 2025-01-04 15:13:58 +00:00
Anthony Stirling
0136c25e1d remove logs 2025-01-04 13:12:34 +00:00
Anthony Stirling
cf03bdc17b test 2025-01-04 12:56:20 +00:00
Anthony Stirling
093b882141 null 2025-01-04 12:46:01 +00:00
Anthony Stirling
46507f10b6 null checks 2025-01-04 12:41:29 +00:00
a
54fbf666fd Merge branch 'filePath' of git@github.com:Stirling-Tools/Stirling-PDF.git into filePath 2025-01-04 12:31:47 +00:00
Anthony Stirling
faecaf1ee4 debugs 2025-01-04 12:30:58 +00:00
Anthony Stirling
a062b36ee5 Update build.yml 2025-01-04 11:34:41 +00:00
Anthony Stirling
00b6d60309 Merge branch 'main' into filePath 2025-01-04 00:48:12 +00:00
pixeebot[bot]
1e0121b4d6 Hardening suggestions for Stirling-PDF / filePath (#2606)
I've reviewed the recently opened PR ([2605 - File paths dynamic
](https://github.com/Stirling-Tools/Stirling-PDF/pull/2605)) and have
identified some area(s) that could benefit from additional hardening
measures.

These changes should help prevent potential security vulnerabilities and
improve overall code quality.

Thank you for your consideration!
🧚🤖  Powered by Pixeebot  

[Feedback](https://ask.pixee.ai/feedback) |
[Community](https://pixee-community.slack.com/signup#/domain-signup) |
[Docs](https://docs.pixee.ai/)
![](https://d1zaessa2hpsmj.cloudfront.net/pixel/v1/track?writeKey=2PI43jNm7atYvAuK7rJUz3Kcd6A&event=PR_HARDENING%7CStirling-Tools%2FStirling-PDF%7C73ac17942fc6504dd1d4e9a8c779e26aba813ac9)

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2025-01-04 00:38:41 +00:00
Anthony Stirling
d7a3708a13 Merge branch 'main' into filePath 2025-01-04 00:20:25 +00:00
Anthony Stirling
73ac17942f finals 2025-01-04 00:19:56 +00:00
Anthony Stirling
54e599a18b Test 2025-01-04 00:09:00 +00:00
181 changed files with 6050 additions and 27687 deletions

View File

@@ -9,7 +9,6 @@ Front End:
- any-glob-to-any-file: 'src/main/resources/templates/**/*'
- any-glob-to-any-file: 'src/main/resources/static/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/UI/**/*'
Java:
- changed-files:
@@ -30,7 +29,6 @@ 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/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: '.github/workflows/dependency-review.yml'
- any-glob-to-any-file: '.github/workflows/scorecards.yml'

View File

@@ -1,34 +1,15 @@
# Description of Changes
# Description
Please provide a summary of the changes, including:
- What was changed
- Why the change was made
- Any challenges encountered
Please provide a summary of the changes, including relevant motivation and context.
Closes #(issue_number)
---
## Checklist
### General
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable)
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed)
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.

51
.github/scripts/check_duplicates.py vendored Normal file
View File

@@ -0,0 +1,51 @@
import sys
def find_duplicate_keys(file_path):
"""
Finds duplicate keys in a properties file and returns their occurrences.
This function reads a properties file, identifies any keys that occur more than
once, and returns a dictionary with these keys and the line numbers of their occurrences.
Parameters:
file_path (str): The path to the properties file to be checked.
Returns:
dict: A dictionary where each key is a duplicated key in the file, and the value is a list
of line numbers where the key occurs.
"""
with open(file_path, "r", encoding="utf-8") as file:
lines = file.readlines()
keys = {}
duplicates = {}
for line_number, line in enumerate(lines, start=1):
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key = line.split("=", 1)[0].strip()
if key in keys:
# If the key already exists, add the current line number
duplicates.setdefault(key, []).append(line_number)
# Also add the first instance of the key if not already done
if keys[key] not in duplicates[key]:
duplicates[key].insert(0, keys[key])
else:
# Store the line number of the first instance of the key
keys[key] = line_number
return duplicates
if __name__ == "__main__":
failed = False
for ar in sys.argv[1:]:
duplicates = find_duplicate_keys(ar)
if duplicates:
for key, lines in duplicates.items():
lines_str = ", ".join(map(str, lines))
print(f"{key} duplicated in {ar} on lines {lines_str}")
failed = True
if failed:
sys.exit(1)

View File

@@ -11,8 +11,6 @@ adjusting the format.
Usage:
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 glob
@@ -21,60 +19,25 @@ import argparse
import re
def find_duplicate_keys(file_path):
"""
Identifies duplicate keys in a .properties file.
:param file_path: Path to the .properties file.
:return: List of tuples (key, first_occurrence_line, duplicate_line).
"""
keys = {}
duplicates = []
with open(file_path, "r", encoding="utf-8") as file:
for line_number, line in enumerate(file, start=1):
stripped_line = line.strip()
# Skip empty lines and comments
if not stripped_line or stripped_line.startswith("#"):
continue
# Split the line into key and value
if "=" in stripped_line:
key, _ = stripped_line.split("=", 1)
key = key.strip()
# Check if the key already exists
if key in keys:
duplicates.append((key, keys[key], line_number))
else:
keys[key] = line_number
return duplicates
# Maximum size for properties files (e.g., 200 KB)
MAX_FILE_SIZE = 200 * 1024
def parse_properties_file(file_path):
"""
Parses a .properties file and returns a structured list of its contents.
:param file_path: Path to the .properties file.
:return: List of dictionaries representing each line in the file.
"""
"""Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers)."""
properties_list = []
with open(file_path, "r", encoding="utf-8") as file:
for line_number, line in enumerate(file, start=1):
stripped_line = line.strip()
# Handle empty lines
# Empty lines
if not stripped_line:
properties_list.append(
{"line_number": line_number, "type": "empty", "content": ""}
)
continue
# Handle comments
# Comments
if stripped_line.startswith("#"):
properties_list.append(
{
@@ -85,7 +48,7 @@ def parse_properties_file(file_path):
)
continue
# Handle key-value pairs
# Key-value pairs
match = re.match(r"^([^=]+)=(.*)$", line)
if match:
key, value = match.groups()
@@ -102,14 +65,9 @@ def parse_properties_file(file_path):
def write_json_file(file_path, updated_properties):
"""
Writes updated properties back to the file in their original format.
:param file_path: Path to the .properties file.
:param updated_properties: List of updated properties to write.
"""
updated_lines = {entry["line_number"]: entry for entry in updated_properties}
# Sort lines by their numbers and retain comments and empty lines
# Sort by line numbers and retain comments and empty lines
all_lines = sorted(set(updated_lines.keys()))
original_format = []
@@ -128,8 +86,8 @@ def write_json_file(file_path, updated_properties):
# Replace entries with those from the current JSON
original_format.append(entry)
# Write the updated content back to the file
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
# Write back in the original format
with open(file_path, "w", encoding="utf-8") as file:
for entry in original_format:
if entry["type"] == "comment":
file.write(f"{entry['content']}\n")
@@ -140,12 +98,6 @@ def write_json_file(file_path, updated_properties):
def update_missing_keys(reference_file, file_list, branch=""):
"""
Updates missing keys in the translation files based on the reference file.
:param reference_file: Path to the reference .properties file.
:param file_list: List of translation files to update.
:param branch: Branch where the files are located.
"""
reference_properties = parse_properties_file(reference_file)
for file_path in file_list:
basename_current_file = os.path.basename(os.path.join(branch, file_path))
@@ -212,14 +164,8 @@ def check_for_differences(reference_file, file_list, branch, actor):
basename_current_file = os.path.basename(os.path.join(branch, file_path))
if (
basename_current_file == basename_reference_file
or (
# 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.startswith(
os.path.join("src", "main", "resources", "messages_")
)
or not file_path.endswith(".properties")
or not basename_current_file.startswith("messages_")
@@ -291,24 +237,6 @@ def check_for_differences(reference_file, file_list, branch, actor):
)
else:
report.append("2. **Test Status:** ✅ **_Passed_**")
if find_duplicate_keys(os.path.join(branch, file_path)):
has_differences = True
output = "\n".join(
[
f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`"
for key, first, duplicate in find_duplicate_keys(
os.path.join(branch, file_path)
)
]
)
report.append("3. **Test Status:** ❌ **_Failed_**")
report.append(" - **Issue:**")
report.append(" - duplicate entries were found:")
report.append(output)
else:
report.append("3. **Test Status:** ✅ **_Passed_**")
report.append("")
report.append("---")
report.append("")
@@ -347,12 +275,6 @@ if __name__ == "__main__":
required=True,
help="Branch name.",
)
parser.add_argument(
"--check-file",
type=str,
required=False,
help="List of changed files, separated by spaces.",
)
parser.add_argument(
"--files",
nargs="+",
@@ -371,14 +293,11 @@ if __name__ == "__main__":
file_list = args.files
if file_list is None:
if args.check_file:
file_list = [args.check_file]
else:
file_list = glob.glob(
os.path.join(
os.getcwd(), "src", "main", "resources", "messages_*.properties"
)
file_list = glob.glob(
os.path.join(
os.getcwd(), "src", "main", "resources", "messages_*.properties"
)
)
update_missing_keys(args.reference_file, file_list)
else:
check_for_differences(args.reference_file, file_list, args.branch, args.actor)

85
.github/scripts/check_tabulator.py vendored Normal file
View File

@@ -0,0 +1,85 @@
"""check_tabulator.py"""
import argparse
import sys
def check_tabs(file_path):
"""
Checks for tabs in the specified file.
Args:
file_path (str): The path to the file to be checked.
Returns:
bool: True if tabs are found, False otherwise.
"""
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
if "\t" in content:
print(f"Tab found in {file_path}")
return True
return False
def replace_tabs_with_spaces(file_path, replace_with=" "):
"""
Replaces tabs with a specified number of spaces in the file.
Args:
file_path (str): The path to the file where tabs will be replaced.
replace_with (str): The character(s) to replace tabs with. Defaults to two spaces.
"""
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
updated_content = content.replace("\t", replace_with)
with open(file_path, "w", encoding="utf-8") as file:
file.write(updated_content)
def main():
"""
Main function to replace tabs with spaces in the provided files.
The replacement character and files to check are taken from command line arguments.
"""
# Create ArgumentParser instance
parser = argparse.ArgumentParser(
description="Replace tabs in files with specified characters."
)
# Define optional argument `--replace_with`
parser.add_argument(
"--replace_with",
default=" ",
help="Character(s) to replace tabs with. Default is two spaces.",
)
# Define argument for file paths
parser.add_argument("files", metavar="FILE", nargs="+", help="Files to process.")
# Parse arguments
args = parser.parse_args()
# Extract replacement characters and files from the parsed arguments
replace_with = args.replace_with
files_checked = args.files
error = False
for file_path in files_checked:
if check_tabs(file_path):
replace_tabs_with_spaces(file_path, replace_with)
error = True
if error:
print("Error: Originally found tabs in HTML files, now replaced.")
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1 +0,0 @@
pre-commit

View File

@@ -1,93 +0,0 @@
#
# 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

View File

@@ -1 +0,0 @@
tomlkit

View File

@@ -1,10 +0,0 @@
#
# 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

View File

@@ -26,8 +26,7 @@ jobs:
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'LaserKaspar' ||
github.event.comment.user.login == 'sbplat' ||
github.event.comment.user.login == 'reecebrowne' ||
github.event.comment.user.login == 'DarioGii'
github.event.comment.user.login == 'reecebrowne'
)
outputs:
pr_number: ${{ steps.get-pr.outputs.pr_number }}
@@ -36,7 +35,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -81,7 +80,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -95,8 +94,8 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "temurin"
java-version: '17'
distribution: 'temurin'
- name: Run Gradle Command
run: ./gradlew clean build
@@ -119,7 +118,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: .
file: ./Dockerfile

View File

@@ -8,8 +8,8 @@ permissions:
contents: read
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: "false" # Add flag to track if cleanup occurred
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: 'false' # Add flag to track if cleanup occurred
jobs:
cleanup:
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit

View File

@@ -13,7 +13,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -49,7 +49,7 @@ jobs:
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@v4
with:
name: test-reports-jdk-${{ matrix.jdk-version }}
path: |
@@ -57,7 +57,7 @@ jobs:
build/test-results/
build/reports/problems/
retention-days: 3
docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' &&
@@ -77,7 +77,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -102,14 +102,12 @@ jobs:
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"
cache: 'pip' # caching pip dependencies
- name: Pip requirements
run: |
pip install --require-hashes -r ./cucumber/requirements.txt
pip install -r ./cucumber/requirements.txt
- name: Run Docker Compose Tests
run: |
chmod +x ./cucumber/test_webpages.sh
chmod +x ./test.sh
./test.sh

View File

@@ -18,7 +18,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -28,7 +28,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"
python-version: "3.x"
- name: Get PR data
id: get-pr-data

View File

@@ -42,7 +42,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit

View File

@@ -6,7 +6,7 @@
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: "Dependency Review"
name: 'Dependency Review'
on: [pull_request]
permissions:
@@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: "Checkout Repository"
- name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Dependency Review"
- name: 'Dependency Review'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0

View File

@@ -18,17 +18,10 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
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
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -49,8 +42,8 @@ jobs:
- name: Set up git config
run: |
git config --global user.name "stirlingbot[bot]"
git config --global user.email "1113334+stirlingbot[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Run git add
run: |
@@ -62,22 +55,32 @@ jobs:
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ steps.generate-token.outputs.token }}
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses"
committer: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
author: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: update-3rd-party-licenses
title: "Update 3rd Party Licenses"
body: |
Auto-generated by StirlingBot
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
labels: licenses,github-actions
draft: false
delete-branch: true
sign-commits: true
- name: Enable Pull Request Automerge
- name: Auto approve
if: steps.cpr.outputs.pull-request-operation == 'created'
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
run: gh pr review --approve "${{ steps.cpr.outputs.pull-request-number }}"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
GH_TOKEN: ${{ secrets.GITHUB_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

View File

@@ -15,7 +15,7 @@ jobs:
issues: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit

View File

@@ -9,137 +9,27 @@ permissions:
contents: read
jobs:
read_versions:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.versionNumber.outputs.versionNumber }}
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 21
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
gradle-version: 8.12
- name: Generate jar (With Security=${{ matrix.enable_security }})
run: ./gradlew clean createExe
env:
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
STIRLING_PDF_DESKTOP_UI: false
- name: Rename binaries
run: |
mkdir ./binaries
mv ./build/launch4j/Stirling-PDF.exe ./binaries/win-Stirling-PDF-portable-Server${{ matrix.file_suffix }}.exe
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
- name: Upload build artifacts
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
retention-days: 1
if-no-files-found: error
name: stirling${{ matrix.file_suffix }}-binaries
path: |
./binaries/*
sign_verify-portable:
needs: [build-portable, read_versions]
runs-on: ubuntu-latest
strategy:
matrix:
enable_security: [true, false]
include:
- enable_security: true
file_suffix: "with-login-"
- enable_security: false
file_suffix: ""
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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-
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:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -162,127 +52,54 @@ jobs:
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
.\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
- name: Build Installer
run: ./gradlew build jpackage -x test --info
env:
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
BROWSER_OPEN: true
# Rename and collect artifacts based on OS
- name: Prepare artifacts
id: prepare
shell: bash
run: |
mkdir ./binaries
if [ "${{ matrix.os }}" = "windows-latest" ]; then
mv "./build/jpackage/Stirling-PDF-${{ needs.read_versions.outputs.version }}.exe" "./binaries/Stirling-PDF-win-installer.exe"
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}"
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
mv "./build/jpackage/Stirling-PDF-${{ needs.read_versions.outputs.versionMac }}.dmg" "./binaries/Stirling-PDF-mac-installer.dmg"
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
else
mv "./build/jpackage/stirling-pdf_${{ needs.read_versions.outputs.version }}-1_amd64.deb" "./binaries/Stirling-PDF-linux-installer.deb"
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
fi
- name: Display structure of downloaded files
run: ls -R ./binaries
- name: Upload build artifacts
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
# Upload installer as artifact for testing
- name: Upload Installer Artifact
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
path: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
retention-days: 1
if-no-files-found: error
name: ${{ matrix.platform }}binaries
path: |
./binaries/*
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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
- name: Upload binaries to release
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
with:
tag_name: v${{ needs.read_versions.outputs.version }}
generate_release_notes: true
files: |
./*signed/*
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}

View File

@@ -1,42 +1,20 @@
name: Pre-commit
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 1"
push:
branches: [main]
permissions:
contents: read
jobs:
pre-commit:
update:
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with:
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: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.generate-token.outputs.app-slug }}[bot]" --jq .id)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- id: committer
run: |
echo "string=${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
@@ -45,33 +23,30 @@ jobs:
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: 3.12
cache: 'pip' # caching pip dependencies
- name: Run Pre-Commit Hooks
run: |
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
- run: pre-commit run --all-files -c .pre-commit-config.yaml
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
continue-on-error: true
- name: Set up git config
run: |
git config --global user.name ${{ steps.generate-token.outputs.app-slug }}[bot]
git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: git add
run: |
git add .
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
- name: Create Pull Request
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ steps.generate-token.outputs.token }}
commit-message: ":file_folder: pre-commit"
committer: ${{ steps.committer.outputs.string }}
author: ${{ steps.committer.outputs.string }}
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "ci: 🤖 format everything with pre-commit"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: pre-commit
title: "🤖 format everything with pre-commit by <${{ steps.generate-token.outputs.app-slug }}>"
title: "🤖 format everything with pre-commit by <github-actions[bot]>"
body: |
Auto-generated by [create-pull-request][1] with **${{ steps.generate-token.outputs.app-slug }}**
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false

View File

@@ -18,7 +18,7 @@ jobs:
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -43,7 +43,7 @@ jobs:
if: github.ref == 'refs/heads/master'
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
with:
cosign-release: "v2.4.1"
cosign-release: 'v2.4.1'
- name: Set up Docker Buildx
id: buildx
@@ -67,7 +67,7 @@ jobs:
password: ${{ github.token }}
- name: Set up QEMU
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- name: Convert repository owner to lowercase
id: repoowner
@@ -89,7 +89,7 @@ jobs:
- name: Build and push main Dockerfile
id: build-push-regular
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
@@ -134,7 +134,7 @@ jobs:
- name: Build and push Dockerfile-ultra-lite
id: build-push-lite
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
if: github.ref != 'refs/heads/main'
with:
context: .
@@ -165,7 +165,7 @@ jobs:
- name: Build and push main Dockerfile fat
id: build-push-fat
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
if: github.ref != 'refs/heads/main'
with:
builder: ${{ steps.buildx.outputs.name }}

View File

@@ -9,8 +9,11 @@ permissions:
contents: read
jobs:
build:
push:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
strategy:
matrix:
enable_security: [true, false]
@@ -19,11 +22,9 @@ jobs:
file_suffix: "-with-login"
- enable_security: false
file_suffix: ""
outputs:
version: ${{ steps.versionNumber.outputs.versionNumber }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -47,134 +48,38 @@ jobs:
- name: Get version number
id: versionNumber
run: |
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Rename binaries
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: Rename binarie
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Debug build artifacts
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
- name: Upload Assets binarie
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: binaries${{ matrix.file_suffix }}
path: |
./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.*
./build/libs/Stirling-PDF${{ matrix.file_suffix }}.*
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
overwrite: true
retention-days: 1
if-no-files-found: error
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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
- name: Upload binaries to release
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
with:
tag_name: v${{ needs.build.outputs.version }}
generate_release_notes: true
files: |
./libs/Stirling-PDF*
./launch4j/Stirling-PDF-Server*
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Rename jar binaries
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
- 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

View File

@@ -10,7 +10,7 @@ on:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: "20 7 * * 2"
- cron: '20 7 * * 2'
push:
branches: ["main"]
permissions: read-all
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -66,7 +66,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: SARIF file
path: results.sarif
@@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
sarif_file: results.sarif

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
@@ -37,4 +37,4 @@ jobs:
only-issue-labels: "more-info-needed"
days-before-pr-stale: -1 # Prevents PRs from being marked as stale
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

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit

View File

@@ -13,116 +13,46 @@ permissions:
contents: read
jobs:
read_bot_entries:
sync-readme:
runs-on: ubuntu-latest
outputs:
userName: ${{ steps.get-user-id.outputs.user_name }}
userEmail: ${{ steps.get-user-id.outputs.user_email }}
committer: ${{ steps.committer.outputs.committer }}
permissions:
contents: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
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: Get GitHub App User ID
id: get-user-id
run: |
USER_NAME="${{ steps.generate-token.outputs.app-slug }}[bot]"
USER_ID=$(gh api "/users/$USER_NAME" --jq .id)
USER_EMAIL="$USER_ID+$USER_NAME@users.noreply.github.com"
echo "user_name=$USER_NAME" >> "$GITHUB_OUTPUT"
echo "user_email=$USER_EMAIL" >> "$GITHUB_OUTPUT"
echo "user-id=$USER_ID" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- id: committer
run: |
COMMITTER="${{ steps.get-user-id.outputs.user_name }} <${{ steps.get-user-id.outputs.user_email }}>"
echo "committer=$COMMITTER" >> "$GITHUB_OUTPUT"
sync-files:
needs: ["read_bot_entries"]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with:
egress-policy: audit
- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
with:
app-id: ${{ vars.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"
cache: 'pip' # caching pip dependencies
- name: Sync translation property files
run: |
python .github/scripts/check_language_properties.py --reference-file "src/main/resources/messages_en_GB.properties" --branch main
python-version: "3.x"
- name: Install dependencies
run: pip install tomlkit
- name: Sync README
run: python scripts/counter_translation.py
- name: Set up git config
run: |
git config --global user.name ${{ needs.read_bot_entries.outputs.userName }}
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Run git add
run: |
git add .
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
- name: Install dependencies
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
- name: Sync README
run: |
python scripts/counter_translation.py
- name: Run git add
run: |
git add .
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
git diff --staged --quiet || git commit -m ":memo: Sync README
> Made via sync_files.yml" || echo "no changes"
- name: Create Pull Request
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ steps.generate-token.outputs.token }}
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files
committer: ${{ needs.read_bot_entries.outputs.committer }}
author: ${{ needs.read_bot_entries.outputs.committer }}
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: sync_readme
title: ":memo: Sync translation files + Update README.md (Translation Progress Table)"
title: ":memo: Update README: Translation Progress Table"
body: |
#### Description
This Pull Request was automatically generated to synchronize updates to translation files and documentation. The changes include:
1. **Synchronization of Translation Files:**
- Updated content based on the latest changes in `messages_en_GB.properties`.
- Ensured consistency between all language files and the reference file.
2. **Update README.md:**
- Generated the translation progress table.
- Displayed the current status of translations for all supported languages.
---
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
@@ -130,6 +60,3 @@ jobs:
delete-branch: true
labels: Documentation,Translation,github-actions
sign-commits: true
add-paths: |
README.md
src/main/resources/messages_*.properties

View File

@@ -1,154 +0,0 @@
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
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

View File

@@ -0,0 +1,72 @@
name: Update Translations
on:
push:
branches: ["main"]
paths:
- "src/main/resources/messages_en_GB.properties"
permissions:
contents: read
jobs:
update-translations-main:
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.x"
- name: Run Python script to check files
id: run-check
run: |
echo "Running Python script to check files..."
python .github/scripts/check_language_properties.py \
--reference-file src/main/resources/messages_en_GB.properties \
--branch main
- name: Set up git config
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Add translation keys
run: |
git add src/main/resources/messages_*.properties
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: update_translation_files
title: "Update translation files"
add-paths: |
src/main/resources/messages_*.properties
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false
delete-branch: true
labels: Translation,github-actions
sign-commits: true

8
.gitignore vendored
View File

@@ -14,17 +14,12 @@ local.properties
.classpath
.project
version.properties
#### Stirling-PDF Files ###
pipeline/watchedFolders/
pipeline/finishedFolders/
#### Stirling-PDF Files ###
customFiles/
configs/
watchedFolders/
clientWebUI/
!cucumber/
!cucumber/exampleFiles/
!cucumber/exampleFiles/example_html.zip
# Gradle
.gradle
@@ -116,7 +111,6 @@ clientWebUI/
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
*.db

View File

@@ -6,10 +6,10 @@ repos:
args:
- --fix
- --line-length=127
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
exclude: (split_photos.py)
- id: ruff-format
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
exclude: (split_photos.py)
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
@@ -19,8 +19,8 @@ repos:
- --ignore-words-list=
- --skip="./.*,*.csv,*.json,*.ambr"
- --quiet-level=2
files: \.(html|css|js|py|md)$
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
files: \.(properties|html|css|js|py|md)$
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile)
- repo: https://github.com/gitleaks/gitleaks
rev: v8.22.0
hooks:
@@ -35,7 +35,23 @@ repos:
hooks:
- id: end-of-file-fixer
files: ^.*(\.js|\.java|\.py|\.yml)$
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
- id: trailing-whitespace
files: ^.*(\.js|\.java|\.py|\.yml)$
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
- repo: local
hooks:
- id: check-duplicate-properties-keys
name: Check Duplicate Properties Keys
entry: python .github/scripts/check_duplicates.py
language: python
files: ^(src)/.+\.properties$
- id: check-html-tabs
name: Check HTML for tabs
description: Ensures HTML/CSS/JS files do not contain tab characters
# args: ["--replace_with= "]
entry: python .github/scripts/check_tabulator.py
language: python
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
files: ^.*(\.html|\.css|\.js)$

View File

@@ -575,42 +575,3 @@ 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.
## 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 spotlessApply --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.

View File

@@ -1,5 +1,5 @@
# Main stage
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
FROM alpine:3.20.3
# Copy necessary files
COPY scripts /scripts

View File

@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
./gradlew clean build
# Main stage
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
FROM alpine:3.20.3
# Copy necessary files
COPY scripts /scripts

View File

@@ -1,5 +1,5 @@
# use alpine
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
FROM alpine:3.21.0@sha256:21dc6063fd678b478f57c0e13f47560d0ea4eeba26dfc947b2a4f81f686b9f45
ARG VERSION_TAG

View File

@@ -60,13 +60,3 @@ 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`).
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
```

View File

@@ -14,9 +14,8 @@
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/)
All information available at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/)
![stirling-home](images/stirling-home.jpg)
@@ -113,50 +112,48 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
## Supported Languages
Stirling-PDF currently supports 39 languages!
Stirling-PDF currently supports 38 languages!
| Language | Progress |
| -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![90%](https://geps.dev/progress/90) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![89%](https://geps.dev/progress/89) |
| Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) |
| Bulgarian (Български) (bg_BG) | ![86%](https://geps.dev/progress/86) |
| Catalan (Català) (ca_CA) | ![81%](https://geps.dev/progress/81) |
| Croatian (Hrvatski) (hr_HR) | ![87%](https://geps.dev/progress/87) |
| Czech (Česky) (cs_CZ) | ![87%](https://geps.dev/progress/87) |
| Danish (Dansk) (da_DK) | ![86%](https://geps.dev/progress/86) |
| Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) |
| Arabic (العربية) (ar_AR) | ![93%](https://geps.dev/progress/93) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![92%](https://geps.dev/progress/92) |
| Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) |
| Bulgarian (Български) (bg_BG) | ![89%](https://geps.dev/progress/89) |
| Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) |
| Croatian (Hrvatski) (hr_HR) | ![90%](https://geps.dev/progress/90) |
| Czech (Česky) (cs_CZ) | ![90%](https://geps.dev/progress/90) |
| Danish (Dansk) (da_DK) | ![89%](https://geps.dev/progress/89) |
| Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![98%](https://geps.dev/progress/98) |
| Hindi (हिंदी) (hi_IN) | ![99%](https://geps.dev/progress/99) |
| Hungarian (Magyar) (hu_HU) | ![96%](https://geps.dev/progress/96) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![87%](https://geps.dev/progress/87) |
| Irish (Gaeilge) (ga_IE) | ![79%](https://geps.dev/progress/79) |
| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
| Hungarian (Magyar) (hu_HU) | ![90%](https://geps.dev/progress/90) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![90%](https://geps.dev/progress/90) |
| Irish (Gaeilge) (ga_IE) | ![82%](https://geps.dev/progress/82) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![90%](https://geps.dev/progress/90) |
| Korean (한국어) (ko_KR) | ![99%](https://geps.dev/progress/99) |
| Norwegian (Norsk) (no_NB) | ![79%](https://geps.dev/progress/79) |
| Persian (فارسی) (fa_IR) | ![95%](https://geps.dev/progress/95) |
| Polish (Polski) (pl_PL) | ![86%](https://geps.dev/progress/86) |
| Portuguese (Português) (pt_PT) | ![98%](https://geps.dev/progress/98) |
| Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) |
| Romanian (Română) (ro_RO) | ![81%](https://geps.dev/progress/81) |
| Russian (Русский) (ru_RU) | ![99%](https://geps.dev/progress/99) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![64%](https://geps.dev/progress/64) |
| Simplified Chinese (简体中文) (zh_CN) | ![90%](https://geps.dev/progress/90) |
| Slovakian (Slovensky) (sk_SK) | ![75%](https://geps.dev/progress/75) |
| Slovenian (Slovenščina) (sl_SI) | ![97%](https://geps.dev/progress/97) |
| Spanish (Español) (es_ES) | ![87%](https://geps.dev/progress/87) |
| Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) |
| Thai (ไทย) (th_TH) | ![86%](https://geps.dev/progress/86) |
| Tibetan (བོད་ཡིག་) (zh_BO) | ![95%](https://geps.dev/progress/95) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) |
| Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) |
| Persian (فارسی) (fa_IR) | ![98%](https://geps.dev/progress/98) |
| Polish (Polski) (pl_PL) | ![89%](https://geps.dev/progress/89) |
| Portuguese (Português) (pt_PT) | ![90%](https://geps.dev/progress/90) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![84%](https://geps.dev/progress/84) |
| Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![66%](https://geps.dev/progress/66) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![77%](https://geps.dev/progress/77) |
| Spanish (Español) (es_ES) | ![90%](https://geps.dev/progress/90) |
| Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) |
| Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![83%](https://geps.dev/progress/83) |
| Ukrainian (Українська) (uk_UA) | ![73%](https://geps.dev/progress/73) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![80%](https://geps.dev/progress/80) |
| Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) |
| Ukrainian (Українська) (uk_UA) | ![75%](https://geps.dev/progress/75) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) |
## Stirling PDF Enterprise

View File

@@ -1,45 +0,0 @@
# 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:

View File

@@ -5,12 +5,14 @@ plugins {
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6"
id "com.diffplug.spotless" version "7.0.2"
id "com.diffplug.spotless" version "6.25.0"
id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.0"
//id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.0"
}
import com.github.jk1.license.render.*
ext {
@@ -19,13 +21,13 @@ ext {
logbackVersion = "1.5.7"
imageioVersion = "3.12.0"
lombokVersion = "1.18.36"
bouncycastleVersion = "1.80"
bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.2"
openSamlVersion = "4.3.2"
}
group = "stirling.software"
version = "0.38.0"
version = "0.36.6"
java {
@@ -35,9 +37,9 @@ java {
repositories {
mavenCentral()
maven { url = "https://jitpack.io" }
maven { url = "https://build.shibboleth.net/maven/releases" }
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
maven { url "https://jitpack.io" }
maven { url "https://build.shibboleth.net/maven/releases" }
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
}
@@ -50,15 +52,13 @@ sourceSets {
java {
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
exclude "stirling/software/SPDF/config/security/**"
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/api/DatabaseController.java"
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
exclude "stirling/software/SPDF/model/AttemptCounter.java"
exclude "stirling/software/SPDF/model/Authority.java"
exclude "stirling/software/SPDF/model/BackupNotFoundException.java"
exclude "stirling/software/SPDF/model/PersistentLogin.java"
exclude "stirling/software/SPDF/model/SessionEntity.java"
exclude "stirling/software/SPDF/model/User.java"
@@ -66,32 +66,10 @@ sourceSets {
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
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/**"
}
}
}
}
@@ -111,15 +89,18 @@ def getMacVersion(String version) {
jpackage {
input = "build/libs"
destination = "${projectDir}/build/jpackage"
mainJar = "Stirling-PDF-${project.version}.jar"
appName = "Stirling-PDF"
appVersion = project.version
vendor = "Stirling-Software"
appDescription = "Stirling PDF - Your Local PDF Editor"
appDescription = "Stirling PDF - Your Local PDF Editor"
mainJar = "Stirling-PDF-${project.version}.jar"
mainClass = "org.springframework.boot.loader.launch.JarLauncher"
icon = "src/main/resources/static/favicon.ico"
verbose = true
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
// JVM Options
javaOptions = [
@@ -127,21 +108,23 @@ jpackage {
"-DSTIRLING_PDF_DESKTOP_UI=true",
"-Djava.awt.headless=false",
"-Dapple.awt.UIElement=true",
"--add-opens=java.base/java.lang=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.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",
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
]
verbose = true
destination = "${projectDir}/build/jpackage"
// Windows-specific configuration
windows {
launcherAsService = false
appVersion = project.version
winConsole = false
launcherAsService = false
appVersion = project.version
winConsole = false
winMenu = true // Creates start menu entry
winShortcut = true // Creates desktop shortcut
winShortcutPrompt = true // Lets user choose whether to create shortcuts
@@ -157,7 +140,7 @@ jpackage {
// macOS-specific configuration
mac {
appVersion = getMacVersion(project.version.toString())
appVersion = getMacVersion(project.version.toString())
icon = "src/main/resources/static/favicon.icns"
type = "dmg"
macPackageIdentifier = "com.stirling.software.pdf"
@@ -181,7 +164,7 @@ jpackage {
// Linux-specific configuration
linux {
appVersion = project.version
appVersion = project.version
icon = "src/main/resources/static/favicon.png"
type = "deb" // Can also use "rpm" for Red Hat-based systems
@@ -229,9 +212,9 @@ launch4j {
outfile="Stirling-PDF.exe"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
headerType = "gui"
headerType = "gui"
} else {
headerType = "console"
headerType = "console"
}
jarTask = tasks.bootJar
@@ -239,11 +222,13 @@ launch4j {
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
} else {
variables=["BROWSER_OPEN=true"]
variables=["BROWSER_OPEN=true"]
}
jreMinVersion="17"
mutexName="Stirling-PDF"
@@ -265,7 +250,7 @@ spotless {
importOrder("java", "javax", "org", "com", "net", "io")
toggleOffOn()
trimTrailingWhitespace()
leadingTabsToSpaces()
indentWithSpaces()
endWithNewline()
}
}
@@ -284,14 +269,14 @@ configurations.all {
}
dependencies {
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:127.3.1"
implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21"
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:127.3.1"
implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21"
}
//security updates
implementation "org.springframework:spring-webmvc:6.2.2"
implementation "org.springframework:spring-webmvc:6.2.1"
implementation("io.github.pixee:java-security-toolkit:1.2.1")
@@ -313,20 +298,18 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation "org.springframework:spring-jdbc:6.2.2"
implementation "org.springframework.session:spring-session-core:$springBootVersion"
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
runtimeOnly "com.h2database:h2:2.3.232"
runtimeOnly "org.postgresql:postgresql:42.7.4"
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0'
@@ -363,7 +346,7 @@ dependencies {
//general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation ("com.opencsv:opencsv:5.10") {
implementation ("com.opencsv:opencsv:5.9") {
exclude group: "commons-logging", module: "commons-logging"
}
@@ -387,7 +370,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.3"
implementation "io.micrometer:micrometer-core:1.14.2"
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0"
@@ -396,8 +379,6 @@ dependencies {
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.5"
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
@@ -421,13 +402,13 @@ task writeVersion {
}
swaggerhubUpload {
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
api = "Stirling-PDF" // The name of your API on SwaggerHub
owner = "Frooodle" // Your SwaggerHub username (or organization name)
version = project.version // The version of your API
inputFile = "./SwaggerDoc.json" // The path to your Swagger docs
token = "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
api "Stirling-PDF" // The name of your API on SwaggerHub
owner "Frooodle" // Your SwaggerHub username (or organization name)
version project.version // The version of your API
inputFile "./SwaggerDoc.json" // The path to your Swagger docs
token "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
oas "3.0.0" // The version of the OpenAPI Specification you"re using
}
jar {

View File

@@ -8,3 +8,4 @@
</body>
</html>

View File

@@ -18,4 +18,4 @@ def after_scenario(context, scenario):
# Remove any temporary files
for temp_file in os.listdir('.'):
if temp_file.startswith('genericNonCustomisableName') or temp_file.startswith('temp_image_'):
os.remove(temp_file)
os.remove(temp_file)

View File

@@ -204,12 +204,4 @@ Feature: API Validation
Then the response status code should be 200
And the response file should have size greater than 100
And the response file should have extension ".pdf"
Scenario: Convert PDF to Markdown format
Given I generate a PDF file as "fileInput"
And the pdf contains 3 pages with random text
When I send the API request to the endpoint "/api/v1/convert/pdf/markdown"
Then the response status code should be 200
And the response file should have size greater than 100
And the response file should have extension ".md"

View File

@@ -1,8 +1,7 @@
import os
import requests
from behave import given, when, then
from pypdf import PdfWriter, PdfReader
from pypdf.errors import PdfReadError
from PyPDF2 import PdfWriter, PdfReader
import io
import random
import string
@@ -43,7 +42,7 @@ def step_use_example_file(context, filePath, fileInput):
context.file_name = filePath.split('/')[-1]
if not hasattr(context, 'files'):
context.files = {}
# Ensure the file exists before opening
try:
example_file = open(filePath, 'rb')
@@ -166,17 +165,17 @@ def step_pdf_contains_pages_with_random_text(context, page_count):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
width, height = letter
for _ in range(page_count):
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
c.drawString(100, height - 100, text)
c.showPage()
c.save()
with open(context.file_name, 'wb') as f:
f.write(buffer.getvalue())
context.files[context.param_name].close()
context.files[context.param_name] = open(context.file_name, 'rb')
@@ -185,16 +184,16 @@ def step_pdf_pages_contain_text(context, text):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
width, height = letter
for _ in range(len(PdfReader(context.file_name).pages)):
c.drawString(100, height - 100, text)
c.showPage()
c.save()
with open(context.file_name, 'wb') as f:
f.write(buffer.getvalue())
context.files[context.param_name].close()
context.files[context.param_name] = open(context.file_name, 'rb')
@@ -346,7 +345,7 @@ def step_check_response_pdf_page_count(context, page_count):
def step_check_response_zip_file_count(context, file_count):
response_file = io.BytesIO(context.response.content)
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"
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
@@ -355,7 +354,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:
actual_doc_count = len(zip_file.namelist())
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
for file_name in zip_file.namelist():
with zip_file.open(file_name) as pdf_file:
reader = PdfReader(pdf_file)

View File

@@ -1,5 +0,0 @@
behave
requests
pypdf
reportlab
PyCryptodome

View File

@@ -1,259 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# 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
behave
requests
PyPDF2
reportlab
PyCryptodome

View File

@@ -1,97 +0,0 @@
#!/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

View File

@@ -1,53 +0,0 @@
/
/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
/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

View File

@@ -1,63 +0,0 @@
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

View File

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

View File

@@ -14,9 +14,9 @@ services:
ports:
- "8080:8080"
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"

View File

@@ -1,7 +0,0 @@
# 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,443 +0,0 @@
<?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="com.datastax.oss" name="java-driver-bom" version="4.15.0"/>
<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>
<ignored-keys>
<ignored-key id="436902AF59EDF60E" reason="Key couldn't be downloaded from any key server"/>
</ignored-keys>
<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="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"/>
<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="33FD4BFD33554634053D73C0C2148900BCD3C2AF" group="org.jetbrains" name="annotations" version="24.0.1"/>
<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="4008F9DFF7DBC968F35F9E712642156411CCE8B3" group="com.vladsch.flexmark"/>
<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" group="org.eclipse.platform" name="org.eclipse.osgi" version="3.18.500"/>
<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" group="com.opencsv" name="opencsv" version="5.10"/>
<trusted-key id="B1F250C1F371EBF0E31E86E30E31BBB30C940D01" group="com.posthog.java" name="posthog" version="1.1.1"/>
<trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314" group="org.apache.commons" name="commons-parent"/>
<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.15.4"/>
<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="7.0.2">
<artifact name="com.diffplug.spotless.gradle.plugin-7.0.2.pom">
<sha256 value="ed1ded77a296a6fe21a50279c926c07be14af7c08c2437d3685c70fcf6bba02d" 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.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="dev.equo.ide" name="solstice" version="1.8.1">
<artifact name="solstice-1.8.1.jar">
<sha256 value="6e5ba2cce813be1d71ccdc2ecf3e49271b14e691bfbbb1a114cf3a30e773b10d" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
</artifact>
<artifact name="solstice-1.8.1.module">
<sha256 value="a676039ea6af08f257b46e07c2bf1571a46a4d4b5b9ccb86c3fc98d07fafea1b" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
</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.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.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.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>

Binary file not shown.

View File

@@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

37
gradlew vendored
View File

@@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -82,11 +80,13 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,29 +133,22 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, 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.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

23
gradlew.bat vendored
View File

@@ -13,8 +13,6 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -28,7 +26,6 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -45,11 +42,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

View File

@@ -75,7 +75,7 @@ def write_readme(progress_list: list[tuple[str, int]]) -> None:
f"![{value}%](https://geps.dev/progress/{value})",
)
with open("README.md", "w", encoding="utf-8", newline="\n") as file:
with open("README.md", "w", encoding="utf-8") as file:
file.writelines(content)
@@ -135,10 +135,9 @@ def compare_files(
# elif "language.direction" in sort_ignore_translation[language]["missing"]:
# sort_ignore_translation[language]["missing"].remove("language.direction")
with (
open(default_file_path, encoding="utf-8") as default_file,
open(file_path, encoding="utf-8") as file,
):
with open(default_file_path, encoding="utf-8") as default_file, open(
file_path, encoding="utf-8"
) as file:
for _ in range(5):
next(default_file)
try:
@@ -196,7 +195,7 @@ def compare_files(
)
)
ignore_translation = convert_to_multiline(sort_ignore_translation)
with open(ignore_translation_file, "w", encoding="utf-8", newline="\n") as file:
with open(ignore_translation_file, "w", encoding="utf-8") as file:
file.write(tomlkit.dumps(ignore_translation))
unique_data = list(set(result_list))

View File

@@ -50,7 +50,6 @@ ignore = [
'pipeline.title',
'pipelineOptions.pipelineHeader',
'pro',
'redact.zoom',
'sponsor',
'text',
'validateSignature.cert.bits',
@@ -211,11 +210,6 @@ ignore = [
'watermark.type.1',
]
[sl_SI]
ignore = [
'language.direction',
]
[sr_LATN_RS]
ignore = [
'language.direction',
@@ -253,11 +247,6 @@ ignore = [
'showJS.tags',
]
[zh_BO]
ignore = [
'language.direction',
]
[zh_CN]
ignore = [
'language.direction',

View File

@@ -27,9 +27,4 @@ public class EEAppConfig {
public boolean runningEnterpriseEdition() {
return licenseKeyChecker.getEnterpriseEnabledResult();
}
@Bean(name = "SSOAutoLogin")
public boolean ssoAutoLogin() {
return applicationProperties.getEnterpriseEdition().isSsoAutoLogin();
}
}

View File

@@ -94,7 +94,7 @@ public class KeygenLicenseVerifier {
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
log.debug(" validateLicenseResponse body: " + response.body());
log.info(" validateLicenseResponse body: " + response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) {

View File

@@ -18,7 +18,7 @@ public class LicenseKeyChecker {
private final ApplicationProperties applicationProperties;
private boolean enterpriseEnabledResult = false;
private boolean enterpriseEnbaledResult = false;
@Autowired
public LicenseKeyChecker(
@@ -35,12 +35,12 @@ public class LicenseKeyChecker {
private void checkLicense() {
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
enterpriseEnabledResult = false;
enterpriseEnbaledResult = false;
} else {
enterpriseEnabledResult =
enterpriseEnbaledResult =
licenseService.verifyLicense(
applicationProperties.getEnterpriseEdition().getKey());
if (enterpriseEnabledResult) {
if (enterpriseEnbaledResult) {
log.info("License key is valid.");
} else {
log.info("License key is invalid.");
@@ -55,6 +55,6 @@ public class LicenseKeyChecker {
}
public boolean getEnterpriseEnabledResult() {
return enterpriseEnabledResult;
return enterpriseEnbaledResult;
}
}

View File

@@ -28,14 +28,13 @@ import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
@Slf4j
@EnableScheduling
@SpringBootApplication
public class SPDFApplication {
@EnableScheduling
@Slf4j
public class SPdfApplication {
private static String serverPortStatic;
private static String baseUrlStatic;
private static String serverPortStatic;
private final Environment env;
private final ApplicationProperties applicationProperties;
private final WebBrowser webBrowser;
@@ -43,7 +42,7 @@ public class SPDFApplication {
@Value("${baseUrl:http://localhost}")
private String baseUrl;
public SPDFApplication(
public SPdfApplication(
Environment env,
ApplicationProperties applicationProperties,
@Autowired(required = false) WebBrowser webBrowser) {
@@ -52,19 +51,33 @@ public class SPDFApplication {
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 {
SpringApplication app = new SpringApplication(SPDFApplication.class);
SpringApplication app = new SpringApplication(SPdfApplication.class);
Properties props = new Properties();
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
System.setProperty("java.awt.headless", "false");
app.setHeadless(false);
props.put("java.awt.headless", "false");
props.put("spring.main.web-application-type", "servlet");
}
app.setAdditionalProfiles(getActiveProfile(args));
app.setAdditionalProfiles("default");
ConfigInitializer initializer = new ConfigInitializer();
try {
@@ -72,8 +85,8 @@ public class SPDFApplication {
} catch (IOException | URISyntaxException e) {
log.error("Error initialising configuration", e);
}
Map<String, String> propertyFiles = new HashMap<>();
Map<String, String> propertyFiles = new HashMap<>();
// External config files
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath());
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) {
@@ -85,7 +98,6 @@ public class SPDFApplication {
"External configuration file '{}' does not exist.",
InstallationPathConfig.getSettingsPath());
}
if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) {
String existingLocation =
propertyFiles.getOrDefault("spring.config.additional-location", "");
@@ -101,21 +113,17 @@ public class SPDFApplication {
InstallationPathConfig.getCustomSettingsPath());
}
Properties finalProps = new Properties();
if (!propertyFiles.isEmpty()) {
finalProps.putAll(
Collections.singletonMap(
"spring.config.additional-location",
propertyFiles.get("spring.config.additional-location")));
}
if (!props.isEmpty()) {
finalProps.putAll(props);
}
app.setDefaultProperties(finalProps);
app.run(args);
// Ensure directories are created
try {
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
@@ -123,10 +131,34 @@ public class SPDFApplication {
} catch (Exception e) {
log.error("Error creating directories: {}", e.getMessage());
}
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
public void init() {
baseUrlStatic = this.baseUrl;
@@ -157,17 +189,6 @@ public class SPDFApplication {
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
public void cleanup() {
if (webBrowser != null) {
@@ -175,55 +196,10 @@ 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() {
return baseUrlStatic;
}
public static String getStaticPort() {
return serverPortStatic;
}
public String getNonStaticPort() {
return serverPortStatic;
}

View File

@@ -126,7 +126,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Convert", "url-to-pdf");
addEndpointToGroup("Convert", "markdown-to-pdf");
addEndpointToGroup("Convert", "pdf-to-csv");
addEndpointToGroup("Convert", "pdf-to-markdown");
// Adding endpoints to "Security" group
addEndpointToGroup("Security", "add-password");
@@ -137,7 +136,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Security", "remove-cert-sign");
addEndpointToGroup("Security", "sanitize-pdf");
addEndpointToGroup("Security", "auto-redact");
addEndpointToGroup("Security", "redact");
// Adding endpoints to "Other" group
addEndpointToGroup("Other", "ocr-pdf");
@@ -183,6 +181,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Python", "extract-image-scans");
addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf");
addEndpointToGroup("Python", "pdf-to-img");
addEndpointToGroup("Python", "file-to-pdf");
// openCV
@@ -236,7 +235,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "markdown-to-pdf");
addEndpointToGroup("Java", "show-javascript");
addEndpointToGroup("Java", "auto-redact");
addEndpointToGroup("Java", "redact");
addEndpointToGroup("Java", "pdf-to-csv");
addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf");
@@ -244,7 +242,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", REMOVE_BLANKS);
addEndpointToGroup("Java", "pdf-to-text");
addEndpointToGroup("Java", "remove-image-pdf");
addEndpointToGroup("Java", "pdf-to-markdown");
// Javascript
addEndpointToGroup("Javascript", "pdf-organizer");
@@ -260,11 +257,9 @@ public class EndpointConfiguration {
// Weasyprint dependent endpoints
addEndpointToGroup("Weasyprint", "html-to-pdf");
addEndpointToGroup("Weasyprint", "url-to-pdf");
addEndpointToGroup("Weasyprint", "markdown-to-pdf");
// Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html");
addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
// disabled for now while we resolve issues
disableEndpoint("pdf-to-pdfa");

View File

@@ -22,7 +22,6 @@ public class InstallationPathConfig {
// 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;
@@ -46,7 +45,6 @@ public class InstallationPathConfig {
// 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;
@@ -120,10 +118,6 @@ public class InstallationPathConfig {
return PIPELINE_FINISHED_FOLDERS_PATH;
}
public static String getPipelineDefaultWebUIConfigsDir() {
return PIPELINE_DEFAULT_WEB_UI_CONFIGS;
}
public static String getStaticPath() {
return STATIC_PATH;
}

View File

@@ -0,0 +1,17 @@
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();
}

View File

@@ -1,17 +0,0 @@
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();
}

View File

@@ -20,7 +20,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
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.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.model.ApplicationProperties;
@@ -110,7 +110,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
// Construct URLs required for SAML configuration
String serverUrl =
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
SPdfApplication.getStaticBaseUrl() + ":" + SPdfApplication.getStaticPort();
String relyingPartyIdentifier =
serverUrl + "/saml2/service-provider-metadata/" + registrationId;

View File

@@ -1,56 +1,49 @@
package stirling.software.SPDF.config.security;
import java.sql.SQLException;
import java.io.IOException;
import java.util.UUID;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Slf4j
@Component
@Slf4j
public class InitialSecuritySetup {
private final UserService userService;
private final ApplicationProperties applicationProperties;
private final DatabaseInterface databaseService;
private final DatabaseBackupInterface databaseBackupHelper;
public InitialSecuritySetup(
UserService userService,
ApplicationProperties applicationProperties,
DatabaseInterface databaseService) {
DatabaseBackupInterface databaseBackupHelper) {
this.userService = userService;
this.applicationProperties = applicationProperties;
this.databaseService = databaseService;
this.databaseBackupHelper = databaseBackupHelper;
}
@PostConstruct
public void init() {
try {
if (databaseService.hasBackup()) {
databaseService.importDatabase();
}
if (!userService.hasUsers()) {
initializeAdminUser();
}
public void init() throws IllegalArgumentException, IOException {
if (databaseBackupHelper.hasBackup() && !userService.hasUsers()) {
databaseBackupHelper.importDatabase();
} else if (!userService.hasUsers()) {
initializeAdminUser();
} else {
databaseBackupHelper.exportDatabase();
userService.migrateOauth2ToSSO();
initializeInternalApiUser();
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
log.error("Failed to initialize security setup.", e);
System.exit(1);
}
initializeInternalApiUser();
}
private void initializeAdminUser() throws SQLException, UnsupportedProviderException {
private void initializeAdminUser() throws IOException {
String initialUsername =
applicationProperties.getSecurity().getInitialLogin().getUsername();
String initialPassword =
@@ -59,34 +52,36 @@ public class InitialSecuritySetup {
&& !initialUsername.isEmpty()
&& initialPassword != null
&& !initialPassword.isEmpty()
&& userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
log.info("Admin user created: {}", initialUsername);
&& !userService.findByUsernameIgnoreCase(initialUsername).isPresent()) {
try {
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
log.info("Admin user created: " + initialUsername);
} catch (IllegalArgumentException e) {
log.error("Failed to initialize security setup", e);
System.exit(1);
}
} else {
createDefaultAdminUser();
}
}
private void createDefaultAdminUser() throws SQLException, UnsupportedProviderException {
private void createDefaultAdminUser() throws IllegalArgumentException, IOException {
String defaultUsername = "admin";
String defaultPassword = "stirling";
if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {
if (!userService.findByUsernameIgnoreCase(defaultUsername).isPresent()) {
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, SQLException, UnsupportedProviderException {
private void initializeInternalApiUser() throws IllegalArgumentException, IOException {
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
userService.saveUser(
Role.INTERNAL_API_USER.getRoleId(),
UUID.randomUUID().toString(),
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());
}

View File

@@ -1,24 +1,39 @@
package stirling.software.SPDF.config.security;
import java.security.cert.X509Certificate;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.opensaml.saml.saml2.core.AuthnRequest;
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.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.configuration.EnableWebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
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.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 org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -28,16 +43,24 @@ import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
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.CustomSaml2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
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.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.PersistentLoginRepository;
@@ -49,7 +72,7 @@ import stirling.software.SPDF.repository.PersistentLoginRepository;
public class SecurityConfiguration {
private final CustomUserDetailsService userDetailsService;
private final UserService userService;
@Lazy private final UserService userService;
@Qualifier("loginEnabled")
private final boolean loginEnabledValue;
@@ -63,10 +86,16 @@ public class SecurityConfiguration {
private final FirstLoginFilter firstLoginFilter;
private final SessionPersistentRegistry sessionRegistry;
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(
PersistentLoginRepository persistentLoginRepository,
CustomUserDetailsService userDetailsService,
@@ -77,12 +106,7 @@ public class SecurityConfiguration {
UserAuthenticationFilter userAuthenticationFilter,
LoginAttemptService loginAttemptService,
FirstLoginFilter firstLoginFilter,
SessionPersistentRegistry sessionRegistry,
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
@Autowired(required = false)
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
@Autowired(required = false)
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
SessionPersistentRegistry sessionRegistry) {
this.userDetailsService = userDetailsService;
this.userService = userService;
this.loginEnabledValue = loginEnabledValue;
@@ -93,9 +117,6 @@ public class SecurityConfiguration {
this.firstLoginFilter = firstLoginFilter;
this.sessionRegistry = sessionRegistry;
this.persistentLoginRepository = persistentLoginRepository;
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
}
@Bean
@@ -253,7 +274,7 @@ public class SecurityConfiguration {
userService,
loginAttemptService))
.userAuthoritiesMapper(
oAuth2userAuthoritiesMapper))
userAuthoritiesMapper()))
.permitAll());
}
// Handle SAML
@@ -270,7 +291,7 @@ public class SecurityConfiguration {
try {
saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
saml2RelyingPartyRegistrations)
relyingPartyRegistrations())
.authenticationManager(
new ProviderManager(authenticationProvider))
.successHandler(
@@ -281,7 +302,8 @@ public class SecurityConfiguration {
.failureHandler(
new CustomSaml2AuthenticationFailureHandler())
.authenticationRequestResolver(
saml2AuthenticationRequestResolver);
authenticationRequestResolver(
relyingPartyRegistrations()));
} catch (Exception e) {
log.error("Error configuring SAML2 login", e);
throw new RuntimeException(e);
@@ -289,11 +311,244 @@ public class SecurityConfiguration {
});
}
} 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());
}
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() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
@@ -301,6 +556,46 @@ public class SecurityConfiguration {
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
public IPRateLimitingFilter rateLimitingFilter() {
// Example limit TODO add config level

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
@@ -21,12 +20,11 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.*;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.repository.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository;
@@ -44,7 +42,7 @@ public class UserService implements UserServiceInterface {
private final SessionPersistentRegistry sessionRegistry;
private final DatabaseInterface databaseService;
private final DatabaseBackupInterface databaseBackupHelper;
private final ApplicationProperties applicationProperties;
@@ -54,14 +52,14 @@ public class UserService implements UserServiceInterface {
PasswordEncoder passwordEncoder,
MessageSource messageSource,
SessionPersistentRegistry sessionRegistry,
DatabaseInterface databaseService,
DatabaseBackupInterface databaseBackupHelper,
ApplicationProperties applicationProperties) {
this.userRepository = userRepository;
this.authorityRepository = authorityRepository;
this.passwordEncoder = passwordEncoder;
this.messageSource = messageSource;
this.sessionRegistry = sessionRegistry;
this.databaseService = databaseService;
this.databaseBackupHelper = databaseBackupHelper;
this.applicationProperties = applicationProperties;
}
@@ -78,7 +76,7 @@ public class UserService implements UserServiceInterface {
// Handle OAUTH2 login and user auto creation.
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
return false;
}
@@ -165,12 +163,12 @@ public class UserService implements UserServiceInterface {
}
public void saveUser(String username, AuthenticationType authenticationType)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
saveUser(username, authenticationType, Role.USER.getRoleId());
}
public void saveUser(String username, AuthenticationType authenticationType, String role)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
@@ -181,11 +179,11 @@ public class UserService implements UserServiceInterface {
user.addAuthority(new Authority(role, user));
user.setAuthenticationType(authenticationType);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void saveUser(String username, String password)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
@@ -195,11 +193,11 @@ public class UserService implements UserServiceInterface {
user.setEnabled(true);
user.setAuthenticationType(AuthenticationType.WEB);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void saveUser(String username, String password, String role, boolean firstLogin)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
@@ -211,11 +209,11 @@ public class UserService implements UserServiceInterface {
user.setAuthenticationType(AuthenticationType.WEB);
user.setFirstLogin(firstLogin);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void saveUser(String username, String password, String role)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
saveUser(username, password, role, false);
}
@@ -249,7 +247,7 @@ public class UserService implements UserServiceInterface {
}
public void updateUserSettings(String username, Map<String, String> updates)
throws SQLException, UnsupportedProviderException {
throws IOException {
Optional<User> userOpt = findByUsernameIgnoreCaseWithSettings(username);
if (userOpt.isPresent()) {
User user = userOpt.get();
@@ -261,7 +259,7 @@ public class UserService implements UserServiceInterface {
settingsMap.putAll(updates);
user.setSettings(settingsMap);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
}
@@ -282,45 +280,38 @@ public class UserService implements UserServiceInterface {
}
public void changeUsername(User user, String newUsername)
throws IllegalArgumentException,
IOException,
SQLException,
UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!isUsernameValid(newUsername)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
user.setUsername(newUsername);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void changePassword(User user, String newPassword)
throws SQLException, UnsupportedProviderException {
public void changePassword(User user, String newPassword) throws IOException {
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void changeFirstUse(User user, boolean firstUse)
throws SQLException, UnsupportedProviderException {
public void changeFirstUse(User user, boolean firstUse) throws IOException {
user.setFirstLogin(firstUse);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void changeRole(User user, String newRole)
throws SQLException, UnsupportedProviderException {
public void changeRole(User user, String newRole) throws IOException {
Authority userAuthority = this.findRole(user);
userAuthority.setAuthority(newRole);
authorityRepository.save(userAuthority);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public void changeUserEnabled(User user, Boolean enbeled)
throws SQLException, UnsupportedProviderException {
public void changeUserEnabled(User user, Boolean enbeled) throws IOException {
user.setEnabled(enbeled);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
public boolean isPasswordCorrect(User user, String currentPassword) {
@@ -329,16 +320,12 @@ public class UserService implements UserServiceInterface {
public boolean isUsernameValid(String username) {
// Checks whether the simple username is formatted correctly
// Regular expression for user name: Min. 3 characters, max. 50 characters
boolean isValidSimpleUsername =
username.matches("^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$");
username.matches("^[a-zA-Z0-9][a-zA-Z0-9@._+-]*[a-zA-Z0-9]$");
// Checks whether the email address is formatted correctly
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
boolean isValidEmail =
username.matches(
"^(?=.{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,})$");
"^(?=.{1,64}@)[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<>();
notAllowedUserList.add("ALL_USERS".toLowerCase());
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
@@ -410,8 +397,7 @@ public class UserService implements UserServiceInterface {
}
@Transactional
public void syncCustomApiUser(String customApiKey)
throws SQLException, UnsupportedProviderException {
public void syncCustomApiUser(String customApiKey) throws IOException {
if (customApiKey == null || customApiKey.trim().length() == 0) {
return;
}
@@ -428,14 +414,14 @@ public class UserService implements UserServiceInterface {
user.setApiKey(customApiKey);
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
} else {
// Update API key if it has changed
User user = existingUser.get();
if (!customApiKey.equals(user.getApiKey())) {
user.setApiKey(customApiKey);
userRepository.save(user);
databaseService.exportDatabase();
databaseBackupHelper.exportDatabase();
}
}
}

View File

@@ -0,0 +1,232 @@
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("|");
}
}

View File

@@ -1,147 +0,0 @@
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");
}
}
}

View File

@@ -1,316 +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.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("|");
}
}

View File

@@ -1,27 +1,21 @@
package stirling.software.SPDF.config.security.database;
import java.sql.SQLException;
import java.io.IOException;
import org.springframework.context.annotation.Conditional;
import org.springframework.scheduling.annotation.Scheduled;
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
@Conditional(H2SQLCondition.class)
public class ScheduledTasks {
private final DatabaseInterface databaseService;
private final DatabaseBackupHelper databaseBackupService;
public ScheduledTasks(DatabaseInterface databaseService) {
this.databaseService = databaseService;
public ScheduledTasks(DatabaseBackupHelper databaseBackupService) {
this.databaseBackupService = databaseBackupService;
}
@Scheduled(cron = "0 0 0 * * ?")
public void performBackup() throws SQLException, UnsupportedProviderException {
databaseService.exportDatabase();
public void performBackup() throws IOException {
databaseBackupService.exportDatabase();
}
}

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
@@ -19,7 +18,6 @@ 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.AuthenticationType;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.utils.RequestUriUtils;
public class CustomOAuth2AuthenticationSuccessHandler
@@ -99,8 +97,10 @@ public class CustomOAuth2AuthenticationSuccessHandler
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
}
response.sendRedirect(contextPath + "/");
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
return;
} catch (IllegalArgumentException e) {
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return;
}
}
}

View File

@@ -1,213 +0,0 @@
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;
};
}
}

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.config.security.saml2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
@@ -19,7 +18,6 @@ import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor
@@ -111,7 +109,7 @@ public class CustomSaml2AuthenticationSuccessHandler
log.debug("Successfully processed authentication for user: {}", username);
response.sendRedirect(contextPath + "/");
return;
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
} catch (IllegalArgumentException e) {
log.debug(
"Invalid username detected for user: {}, redirecting to logout",
username);

View File

@@ -1,136 +0,0 @@
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;
}
}

View File

@@ -8,7 +8,6 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.eclipse.jetty.http.HttpStatus;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -25,20 +24,19 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.database.DatabaseService;
import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
@Slf4j
@Controller
@RequestMapping("/api/v1/database")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Conditional(H2SQLCondition.class)
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
public class DatabaseController {
private final DatabaseService databaseService;
private final DatabaseBackupHelper databaseBackupHelper;
public DatabaseController(DatabaseService databaseService) {
this.databaseService = databaseService;
public DatabaseController(DatabaseBackupHelper databaseBackupHelper) {
this.databaseBackupHelper = databaseBackupHelper;
}
@Operation(
@@ -59,7 +57,7 @@ public class DatabaseController {
Path tempTemplatePath = Files.createTempFile("backup_", ".sql");
try (InputStream in = file.getInputStream()) {
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
boolean importSuccess = databaseService.importDatabaseFromUI(tempTemplatePath);
boolean importSuccess = databaseBackupHelper.importDatabaseFromUI(tempTemplatePath);
if (importSuccess) {
redirectAttributes.addAttribute("infoMessage", "importIntoDatabaseSuccessed");
} else {
@@ -79,20 +77,21 @@ public class DatabaseController {
@GetMapping("/import-database-file/{fileName}")
public String importDatabaseFromBackupUI(
@Parameter(description = "Name of the file to import", required = true) @PathVariable
String fileName) {
String fileName)
throws IOException {
if (fileName == null || fileName.isEmpty()) {
return "redirect:/database?error=fileNullOrEmpty";
}
// Check if the file exists in the backup list
boolean fileExists =
databaseService.getBackupList().stream()
databaseBackupHelper.getBackupList().stream()
.anyMatch(backup -> backup.getFileName().equals(fileName));
if (!fileExists) {
log.error("File {} not found in backup list", fileName);
return "redirect:/database?error=fileNotFound";
}
log.info("Received file: {}", fileName);
if (databaseService.importDatabaseFromUI(fileName)) {
if (databaseBackupHelper.importDatabaseFromUI(fileName)) {
log.info("File {} imported to database", fileName);
return "redirect:/database?infoMessage=importIntoDatabaseSuccessed";
}
@@ -111,7 +110,7 @@ public class DatabaseController {
throw new IllegalArgumentException("File must not be null or empty");
}
try {
if (databaseService.deleteBackupFile(fileName)) {
if (databaseBackupHelper.deleteBackupFile(fileName)) {
log.info("Deleted file: {}", fileName);
} else {
log.error("Failed to delete file: {}", fileName);
@@ -136,7 +135,7 @@ public class DatabaseController {
throw new IllegalArgumentException("File must not be null or empty");
}
try {
Path filePath = databaseService.getBackupFilePath(fileName);
Path filePath = databaseBackupHelper.getBackupFilePath(fileName);
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
@@ -158,9 +157,14 @@ public class DatabaseController {
+ " database management page.")
@GetMapping("/createDatabaseBackup")
public String createDatabaseBackup() {
log.info("Starting database backup creation...");
databaseService.exportDatabase();
log.info("Database backup successfully created.");
try {
log.info("Starting database backup creation...");
databaseBackupHelper.exportDatabase();
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";
}
}

View File

@@ -1,19 +0,0 @@
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));
}
}

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException;
import java.security.Principal;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -34,7 +33,6 @@ import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UsernameAndPass;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Controller
@Tag(name = "User", description = "User APIs")
@@ -54,7 +52,7 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register")
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
throws SQLException, UnsupportedProviderException {
throws IOException {
if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {
model.addAttribute("error", "Username already exists");
return "register";
@@ -76,7 +74,7 @@ public class UserController {
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes)
throws IOException, SQLException, UnsupportedProviderException {
throws IOException {
if (!userService.isUsernameValid(newUsername)) {
return new RedirectView("/account?messageType=invalidUsername", true);
}
@@ -119,7 +117,7 @@ public class UserController {
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes)
throws SQLException, UnsupportedProviderException {
throws IOException {
if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
}
@@ -147,7 +145,7 @@ public class UserController {
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes)
throws SQLException, UnsupportedProviderException {
throws IOException {
if (principal == null) {
return new RedirectView("/account?messageType=notAuthenticated", true);
}
@@ -168,7 +166,7 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/updateUserSettings")
public String updateUserSettings(HttpServletRequest request, Principal principal)
throws SQLException, UnsupportedProviderException {
throws IOException {
Map<String, String[]> paramMap = request.getParameterMap();
Map<String, String> updates = new HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
@@ -190,7 +188,7 @@ public class UserController {
@RequestParam(name = "authType") String authType,
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
boolean forceChange)
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
throws IllegalArgumentException, IOException {
if (!userService.isUsernameValid(username)) {
return new RedirectView("/addUsers?messageType=invalidUsername", true);
}
@@ -234,7 +232,7 @@ public class UserController {
@RequestParam(name = "username") String username,
@RequestParam(name = "role") String role,
Authentication authentication)
throws SQLException, UnsupportedProviderException {
throws IOException {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
if (!userOpt.isPresent()) {
return new RedirectView("/addUsers?messageType=userNotFound", true);
@@ -272,7 +270,7 @@ public class UserController {
@PathVariable("username") String username,
@RequestParam("enabled") boolean enabled,
Authentication authentication)
throws SQLException, UnsupportedProviderException {
throws IOException {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
if (!userOpt.isPresent()) {
return new RedirectView("/addUsers?messageType=userNotFound", true);

View File

@@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
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.Role;
@@ -44,7 +44,7 @@ public class ApiDocService {
private String getApiDocsUrl() {
String contextPath = servletContext.getContextPath();
String port = SPDFApplication.getStaticPort();
String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/v1/api-docs";
}

View File

@@ -30,7 +30,7 @@ import io.github.pixee.security.ZipSecurity;
import jakarta.servlet.ServletContext;
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.PipelineOperation;
import stirling.software.SPDF.model.Role;
@@ -80,7 +80,7 @@ public class PipelineProcessor {
private String getBaseUrl() {
String contextPath = servletContext.getContextPath();
String port = SPDFApplication.getStaticPort();
String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/";
}

View File

@@ -3,18 +3,13 @@ package stirling.software.SPDF.controller.api.security;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -27,15 +22,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PDFText;
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactionArea;
import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
import stirling.software.SPDF.utils.propertyeditor.StringToArrayListPropertyEditor;
@RestController
@RequestMapping("/api/v1/security")
@@ -50,120 +41,6 @@ public class RedactController {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
List.class, "redactions", new StringToArrayListPropertyEditor());
}
@PostMapping(value = "/redact", consumes = "multipart/form-data")
@Operation(
summary = "Redacts areas and pages in a PDF document",
description =
"This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
List<RedactionArea> redactionAreas = request.getRedactions();
PDDocument document = pdfDocumentFactory.load(file);
PDPageTree allPages = document.getDocumentCatalog().getPages();
redactPages(request, document, allPages);
redactAreas(redactionAreas, document, allPages);
if (request.isConvertPDFToImage()) {
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
document.close();
document = convertedPdf;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
document.close();
byte[] pdfContent = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(
pdfContent,
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_redacted.pdf");
}
private void redactAreas(
List<RedactionArea> redactionAreas, PDDocument document, PDPageTree allPages)
throws IOException {
Color redactColor = null;
for (RedactionArea redactionArea : redactionAreas) {
if (redactionArea.getPage() == null
|| redactionArea.getPage() <= 0
|| redactionArea.getHeight() == null
|| redactionArea.getHeight() <= 0.0D
|| redactionArea.getWidth() == null
|| redactionArea.getWidth() <= 0.0D) continue;
PDPage page = allPages.get(redactionArea.getPage() - 1);
PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);
contentStream.setNonStrokingColor(redactColor);
float x = redactionArea.getX().floatValue();
float y = redactionArea.getY().floatValue();
float width = redactionArea.getWidth().floatValue();
float height = redactionArea.getHeight().floatValue();
PDRectangle box = page.getBBox();
contentStream.addRect(x, box.getHeight() - y - height, width, height);
contentStream.fill();
contentStream.close();
}
}
private void redactPages(
ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
throws IOException {
Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);
List<Integer> pageNumbers = getPageNumbers(request, allPages.getCount());
for (Integer pageNumber : pageNumbers) {
PDPage page = allPages.get(pageNumber);
PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
contentStream.setNonStrokingColor(redactColor);
PDRectangle box = page.getBBox();
contentStream.addRect(0, 0, box.getWidth(), box.getHeight());
contentStream.fill();
contentStream.close();
}
}
private Color decodeOrDefault(String hex, Color defaultColor) {
Color color = null;
try {
color = Color.decode(hex);
} catch (Exception e) {
color = defaultColor;
}
return color;
}
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
String pageNumbersInput = request.getPageNumbers();
String[] parsedPageNumbers =
pageNumbersInput != null ? pageNumbersInput.split(",") : new String[0];
List<Integer> pageNumbers =
GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);
Collections.sort(pageNumbers);
return pageNumbers;
}
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
@Operation(
summary = "Redacts listOfText in a PDF document",

View File

@@ -14,11 +14,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.util.Store;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -214,7 +214,6 @@ public class WatermarkController {
+ Math.abs(watermarkHeight * Math.cos(radians)));
// Calculating the number of rows and columns.
int watermarkRows = (int) (pageHeight / newWatermarkHeight + 1);
int watermarkCols = (int) (pageWidth / newWatermarkWidth + 1);

View File

@@ -44,13 +44,6 @@ public class ConverterWebController {
return "convert/markdown-to-pdf";
}
@GetMapping("/pdf-to-markdown")
@Hidden
public String convertPdfToMarkdownForm(Model model) {
model.addAttribute("currentPage", "pdf-to-markdown");
return "convert/pdf-to-markdown";
}
@GetMapping("/url-to-pdf")
@Hidden
public String convertURLToPdfForm(Model model) {

View File

@@ -11,17 +11,17 @@ import org.springframework.web.bind.annotation.GetMapping;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import stirling.software.SPDF.config.security.database.DatabaseService;
import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
import stirling.software.SPDF.utils.FileInfo;
@Controller
@Tag(name = "Database Management", description = "Database management and security APIs")
public class DatabaseWebController {
private final DatabaseService databaseService;
private final DatabaseBackupHelper databaseBackupHelper;
public DatabaseWebController(DatabaseService databaseService) {
this.databaseService = databaseService;
public DatabaseWebController(DatabaseBackupHelper databaseBackupHelper) {
this.databaseBackupHelper = databaseBackupHelper;
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@@ -34,12 +34,9 @@ public class DatabaseWebController {
} else if (confirmed != null) {
model.addAttribute("infoMessage", confirmed);
}
List<FileInfo> backupList = databaseService.getBackupList();
List<FileInfo> backupList = databaseBackupHelper.getBackupList();
model.addAttribute("backupFiles", backupList);
model.addAttribute("databaseVersion", databaseService.getH2Version());
if ("Unknown".equalsIgnoreCase(databaseService.getH2Version())) {
model.addAttribute("infoMessage", "notSupported");
}
model.addAttribute("databaseVersion", databaseBackupHelper.getH2Version());
return "database";
}
}

View File

@@ -54,11 +54,8 @@ public class GeneralWebController {
model.addAttribute("currentPage", "pipeline");
List<String> pipelineConfigs = new ArrayList<>();
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
if (new File(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()).exists()) {
try (Stream<Path> paths =
Files.walk(
Paths.get(
InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()))) {
if (new File("./pipeline/defaultWebUIConfigs/").exists()) {
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
List<Path> jsonFiles =
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".json"))

View File

@@ -18,12 +18,6 @@ public class SecurityWebController {
return "security/auto-redact";
}
@GetMapping("/redact")
public String redactForm(Model model) {
model.addAttribute("currentPage", "redact");
return "security/redact";
}
@GetMapping("/add-password")
@Hidden
public String addPasswordForm(Model model) {

View File

@@ -112,6 +112,23 @@ public class ApplicationProperties {
return saml2.getEnabled() || oauth2.getEnabled();
}
public boolean isUserPass() {
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
}
public boolean isOauth2Activ() {
return (oauth2 != null
&& oauth2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public boolean isSaml2Activ() {
return (saml2 != null
&& saml2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public enum LoginMethods {
ALL("all"),
NORMAL("normal"),
@@ -130,23 +147,6 @@ public class ApplicationProperties {
}
}
public boolean isUserPass() {
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
}
public boolean isOauth2Activ() {
return (oauth2 != null
&& oauth2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public boolean isSaml2Activ() {
return (saml2 != null
&& saml2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
@Data
public static class InitialLogin {
private String username;
@@ -282,42 +282,6 @@ public class ApplicationProperties {
private String tessdataDir;
private Boolean enableAlphaFunctionality;
private String enableAnalytics;
private Datasource datasource;
}
@Data
public static class Datasource {
private boolean enableCustomDatabase;
private String customDatabaseUrl;
private String type;
private String hostName;
private Integer port;
private String name;
private String username;
@ToString.Exclude private String password;
}
public enum Driver {
H2("h2"),
POSTGRESQL("postgresql"),
ORACLE("oracle"),
MYSQL("mysql");
private final String driverName;
Driver(String driverName) {
this.driverName = driverName;
}
@Override
public String toString() {
return """
Driver {
driverName='%s'
}
"""
.formatted(driverName);
}
}
@Data
@@ -366,7 +330,6 @@ public class ApplicationProperties {
private boolean enabled;
@ToString.Exclude private String key;
private int maxUsers;
private boolean ssoAutoLogin;
private CustomMetadata customMetadata = new CustomMetadata();
@Data

View File

@@ -5,6 +5,7 @@ import java.util.Date;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import lombok.Data;
@@ -14,7 +15,7 @@ import lombok.Data;
public class SessionEntity implements Serializable {
@Id private String sessionId;
private String principalName;
@Lob private String principalName;
private Date lastRequest;

View File

@@ -47,7 +47,7 @@ public class User implements Serializable {
@ElementCollection
@MapKeyColumn(name = "setting_key")
@Lob
@Column(name = "setting_value", columnDefinition = "text")
@Column(name = "setting_value", columnDefinition = "CLOB")
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings.

View File

@@ -1,32 +0,0 @@
package stirling.software.SPDF.model.api.converters;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.PDFToFile;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
public class ConvertPDFToMarkdown {
@PostMapping(consumes = "multipart/form-data", value = "/pdf/markdown")
@Operation(
summary = "Convert PDF to Markdown",
description =
"This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO")
public ResponseEntity<byte[]> processPdfToMarkdown(@ModelAttribute PDFFile request)
throws Exception {
MultipartFile inputFile = request.getFileInput();
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToMarkdown(inputFile);
}
}

View File

@@ -26,7 +26,7 @@ public class OptimizePdfRequest extends PDFFile {
@Schema(
description =
"Whether to normalize the PDF content for better compatibility. Default is false.",
defaultValue = "false")
private Boolean normalize = false;
"Whether to normalize the PDF content for better compatibility. Default is true.",
defaultValue = "true")
private Boolean normalize = true;
}

View File

@@ -1,22 +0,0 @@
package stirling.software.SPDF.model.api.security;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.SPDF.model.api.PDFWithPageNums;
@Data
@EqualsAndHashCode(callSuper = true)
public class ManualRedactPdfRequest extends PDFWithPageNums {
@Schema(description = "A list of areas that should be redacted")
private List<RedactionArea> redactions;
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
private boolean convertPDFToImage;
@Schema(description = "The color used to fully redact certain pages")
private String pageRedactionColor;
}

View File

@@ -1,26 +0,0 @@
package stirling.software.SPDF.model.api.security;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class RedactionArea {
@Schema(description = "The left edge point of the area to be redacted.")
private Double x;
@Schema(description = "The top edge point of the area to be redacted.")
private Double y;
@Schema(description = "The height of the area to be redacted.")
private Double height;
@Schema(description = "The width of the area to be redacted.")
private Double width;
@Schema(description = "The page on which the area should be redacted.")
private Integer page;
@Schema(description = "The color used to redact the specified area.")
private String color;
}

View File

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

View File

@@ -20,9 +20,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet;
import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j;
@@ -31,123 +28,6 @@ import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@Slf4j
public class PDFToFile {
public ResponseEntity<byte[]> processPdfToMarkdown(MultipartFile inputFile)
throws IOException, InterruptedException {
if (!"application/pdf".equals(inputFile.getContentType())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
MutableDataSet options =
new MutableDataSet()
.set(
FlexmarkHtmlConverter.MAX_BLANK_LINES,
2) // Control max consecutive blank lines
.set(
FlexmarkHtmlConverter.MAX_TRAILING_BLANK_LINES,
1) // Control trailing blank lines
.set(
FlexmarkHtmlConverter.SETEXT_HEADINGS,
true) // Use Setext headings for h1 and h2
.set(
FlexmarkHtmlConverter.OUTPUT_UNKNOWN_TAGS,
false) // Don't output HTML for unknown tags
.set(
FlexmarkHtmlConverter.TYPOGRAPHIC_QUOTES,
true) // Convert quotation marks
.set(
FlexmarkHtmlConverter.BR_AS_PARA_BREAKS,
true) // Convert <br> to paragraph breaks
.set(FlexmarkHtmlConverter.CODE_INDENT, " "); // Indent for code blocks
FlexmarkHtmlConverter htmlToMarkdownConverter =
FlexmarkHtmlConverter.builder(options).build();
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
String pdfBaseName = originalPdfFileName;
if (originalPdfFileName.contains(".")) {
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
}
Path tempInputFile = null;
Path tempOutputDir = null;
byte[] fileBytes;
String fileName = "temp.file";
try {
tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile);
tempOutputDir = Files.createTempDirectory("output_");
List<String> command =
new ArrayList<>(
Arrays.asList(
"pdftohtml",
"-s",
"-noframes",
"-c",
tempInputFile.toString(),
pdfBaseName));
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
// Process HTML files to Markdown
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
List<File> markdownFiles = new ArrayList<>();
// Convert HTML files to Markdown
for (File outputFile : outputFiles) {
if (outputFile.getName().endsWith(".html")) {
String html = Files.readString(outputFile.toPath());
String markdown = htmlToMarkdownConverter.convert(html);
String mdFileName = outputFile.getName().replace(".html", ".md");
File mdFile = new File(tempOutputDir.toFile(), mdFileName);
Files.writeString(mdFile.toPath(), markdown);
markdownFiles.add(mdFile);
}
}
// If there's only one markdown file, return it directly
if (markdownFiles.size() == 1) {
fileName = pdfBaseName + ".md";
fileBytes = Files.readAllBytes(markdownFiles.get(0).toPath());
} else {
// Multiple files - create a zip
fileName = pdfBaseName + "ToMarkdown.zip";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// Add markdown files
for (File mdFile : markdownFiles) {
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
zipOutputStream.putNextEntry(mdEntry);
Files.copy(mdFile.toPath(), zipOutputStream);
zipOutputStream.closeEntry();
}
// Add images and other assets
for (File file : outputFiles) {
if (!file.getName().endsWith(".html") && !file.getName().endsWith(".md")) {
ZipEntry assetEntry = new ZipEntry(file.getName());
zipOutputStream.putNextEntry(assetEntry);
Files.copy(file.toPath(), zipOutputStream);
zipOutputStream.closeEntry();
}
}
}
fileBytes = byteArrayOutputStream.toByteArray();
}
} finally {
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
}
return WebResponseUtils.bytesToWebResponse(
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
}
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
throws IOException, InterruptedException {
if (!"application/pdf".equals(inputFile.getContentType())) {

View File

@@ -1,37 +0,0 @@
package stirling.software.SPDF.utils.propertyeditor;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.RedactionArea;
@Slf4j
public class StringToArrayListPropertyEditor extends PropertyEditorSupport {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.trim().isEmpty()) {
setValue(new ArrayList<>());
return;
}
try {
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
TypeReference<ArrayList<RedactionArea>> typeRef =
new TypeReference<ArrayList<RedactionArea>>() {};
List<RedactionArea> list = objectMapper.readValue(text, typeRef);
setValue(list);
} catch (Exception e) {
log.error("Exception while converting {}", e);
throw new IllegalArgumentException(
"Failed to convert java.lang.String to java.util.List");
}
}
}

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