Compare commits
4 Commits
update-3rd
...
autoMergeT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d797d700db | ||
|
|
ff1086b0d5 | ||
|
|
eb3c3cace0 | ||
|
|
b2d1f20ebe |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,2 +1,2 @@
|
|||||||
# All PRs to V1 must be approved by Frooodle
|
# All PRs to V1 must be approved by Frooodle
|
||||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh
|
* @Frooodle @reecebrowne @Ludy87 @DarioGii
|
||||||
|
|||||||
29
.github/pull_request_template.md
vendored
29
.github/pull_request_template.md
vendored
@@ -1,34 +1,15 @@
|
|||||||
# Description of Changes
|
# Description
|
||||||
|
|
||||||
Please provide a summary of the changes, including:
|
Please provide a summary of the changes, including relevant motivation and context.
|
||||||
|
|
||||||
- What was changed
|
|
||||||
- Why the change was made
|
|
||||||
- Any challenges encountered
|
|
||||||
|
|
||||||
Closes #(issue_number)
|
Closes #(issue_number)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
### General
|
|
||||||
|
|
||||||
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
- [ ] 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 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
|
- [ ] 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)
|
- [ ] 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.
|
|
||||||
|
|||||||
6
.github/release.yml
vendored
6
.github/release.yml
vendored
@@ -1,4 +1,10 @@
|
|||||||
changelog:
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- Documentation
|
||||||
|
- Test
|
||||||
|
- Github
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
- title: Bug Fixes
|
- title: Bug Fixes
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
51
.github/scripts/check_duplicates.py
vendored
Normal file
51
.github/scripts/check_duplicates.py
vendored
Normal 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)
|
||||||
122
.github/scripts/check_language_properties.py
vendored
122
.github/scripts/check_language_properties.py
vendored
@@ -11,8 +11,6 @@ adjusting the format.
|
|||||||
Usage:
|
Usage:
|
||||||
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
|
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
|
||||||
"""
|
"""
|
||||||
# Sample for Windows:
|
|
||||||
# python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_de_DE.properties src\main\resources\messages_uk_UA.properties
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import glob
|
import glob
|
||||||
@@ -21,60 +19,25 @@ import argparse
|
|||||||
import re
|
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)
|
# Maximum size for properties files (e.g., 200 KB)
|
||||||
MAX_FILE_SIZE = 200 * 1024
|
MAX_FILE_SIZE = 200 * 1024
|
||||||
|
|
||||||
|
|
||||||
def parse_properties_file(file_path):
|
def parse_properties_file(file_path):
|
||||||
"""
|
"""Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers)."""
|
||||||
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.
|
|
||||||
"""
|
|
||||||
properties_list = []
|
properties_list = []
|
||||||
with open(file_path, "r", encoding="utf-8") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
for line_number, line in enumerate(file, start=1):
|
for line_number, line in enumerate(file, start=1):
|
||||||
stripped_line = line.strip()
|
stripped_line = line.strip()
|
||||||
|
|
||||||
# Handle empty lines
|
# Empty lines
|
||||||
if not stripped_line:
|
if not stripped_line:
|
||||||
properties_list.append(
|
properties_list.append(
|
||||||
{"line_number": line_number, "type": "empty", "content": ""}
|
{"line_number": line_number, "type": "empty", "content": ""}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle comments
|
# Comments
|
||||||
if stripped_line.startswith("#"):
|
if stripped_line.startswith("#"):
|
||||||
properties_list.append(
|
properties_list.append(
|
||||||
{
|
{
|
||||||
@@ -85,7 +48,7 @@ def parse_properties_file(file_path):
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle key-value pairs
|
# Key-value pairs
|
||||||
match = re.match(r"^([^=]+)=(.*)$", line)
|
match = re.match(r"^([^=]+)=(.*)$", line)
|
||||||
if match:
|
if match:
|
||||||
key, value = match.groups()
|
key, value = match.groups()
|
||||||
@@ -102,14 +65,9 @@ def parse_properties_file(file_path):
|
|||||||
|
|
||||||
|
|
||||||
def write_json_file(file_path, updated_properties):
|
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}
|
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()))
|
all_lines = sorted(set(updated_lines.keys()))
|
||||||
|
|
||||||
original_format = []
|
original_format = []
|
||||||
@@ -128,8 +86,8 @@ def write_json_file(file_path, updated_properties):
|
|||||||
# Replace entries with those from the current JSON
|
# Replace entries with those from the current JSON
|
||||||
original_format.append(entry)
|
original_format.append(entry)
|
||||||
|
|
||||||
# Write the updated content back to the file
|
# Write back in the original format
|
||||||
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
|
with open(file_path, "w", encoding="utf-8") as file:
|
||||||
for entry in original_format:
|
for entry in original_format:
|
||||||
if entry["type"] == "comment":
|
if entry["type"] == "comment":
|
||||||
file.write(f"{entry['content']}\n")
|
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=""):
|
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)
|
reference_properties = parse_properties_file(reference_file)
|
||||||
for file_path in file_list:
|
for file_path in file_list:
|
||||||
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
||||||
@@ -164,7 +116,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
|
|||||||
if current_entry["type"] == "entry":
|
if current_entry["type"] == "entry":
|
||||||
if ref_entry_copy["type"] != "entry":
|
if ref_entry_copy["type"] != "entry":
|
||||||
continue
|
continue
|
||||||
if ref_entry_copy["key"].lower() == current_entry["key"].lower():
|
if ref_entry_copy["key"] == current_entry["key"]:
|
||||||
ref_entry_copy["value"] = current_entry["value"]
|
ref_entry_copy["value"] = current_entry["value"]
|
||||||
updated_properties.append(ref_entry_copy)
|
updated_properties.append(ref_entry_copy)
|
||||||
write_json_file(os.path.join(branch, file_path), updated_properties)
|
write_json_file(os.path.join(branch, file_path), updated_properties)
|
||||||
@@ -199,30 +151,23 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
|
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
|
||||||
|
|
||||||
for file_path in file_arr:
|
for file_path in file_arr:
|
||||||
file_normpath = os.path.normpath(file_path)
|
absolute_path = os.path.abspath(file_path)
|
||||||
absolute_path = os.path.abspath(file_normpath)
|
|
||||||
# Verify that file is within the expected directory
|
# Verify that file is within the expected directory
|
||||||
if not absolute_path.startswith(base_dir):
|
if not absolute_path.startswith(base_dir):
|
||||||
raise ValueError(f"Unsafe file found: {file_normpath}")
|
raise ValueError(f"Unsafe file found: {file_path}")
|
||||||
# Verify file size before processing
|
# Verify file size before processing
|
||||||
if os.path.getsize(os.path.join(branch, file_normpath)) > MAX_FILE_SIZE:
|
if os.path.getsize(os.path.join(branch, file_path)) > MAX_FILE_SIZE:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The file {file_normpath} is too large and could pose a security risk."
|
f"The file {file_path} is too large and could pose a security risk."
|
||||||
)
|
)
|
||||||
|
|
||||||
basename_current_file = os.path.basename(os.path.join(branch, file_normpath))
|
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
||||||
if (
|
if (
|
||||||
basename_current_file == basename_reference_file
|
basename_current_file == basename_reference_file
|
||||||
or (
|
or not file_path.startswith(
|
||||||
# only local windows command
|
os.path.join("src", "main", "resources", "messages_")
|
||||||
not file_normpath.startswith(
|
|
||||||
os.path.join("", "src", "main", "resources", "messages_")
|
|
||||||
)
|
|
||||||
and not file_normpath.startswith(
|
|
||||||
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
or not file_normpath.endswith(".properties")
|
or not file_path.endswith(".properties")
|
||||||
or not basename_current_file.startswith("messages_")
|
or not basename_current_file.startswith("messages_")
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
@@ -292,24 +237,6 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
report.append("2. **Test Status:** ✅ **_Passed_**")
|
report.append("2. **Test Status:** ✅ **_Passed_**")
|
||||||
|
|
||||||
if find_duplicate_keys(os.path.join(branch, file_normpath)):
|
|
||||||
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_normpath)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
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("---")
|
report.append("---")
|
||||||
report.append("")
|
report.append("")
|
||||||
@@ -348,12 +275,6 @@ if __name__ == "__main__":
|
|||||||
required=True,
|
required=True,
|
||||||
help="Branch name.",
|
help="Branch name.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--check-file",
|
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="List of changed files, separated by spaces.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--files",
|
"--files",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
@@ -372,14 +293,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
file_list = args.files
|
file_list = args.files
|
||||||
if file_list is None:
|
if file_list is None:
|
||||||
if args.check_file:
|
file_list = glob.glob(
|
||||||
file_list = [args.check_file]
|
os.path.join(
|
||||||
else:
|
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)
|
update_missing_keys(args.reference_file, file_list)
|
||||||
else:
|
else:
|
||||||
check_for_differences(args.reference_file, file_list, args.branch, args.actor)
|
check_for_differences(args.reference_file, file_list, args.branch, args.actor)
|
||||||
|
|||||||
85
.github/scripts/check_tabulator.py
vendored
Normal file
85
.github/scripts/check_tabulator.py
vendored
Normal 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()
|
||||||
14
.github/workflows/PR-Demo-Comment.yml
vendored
14
.github/workflows/PR-Demo-Comment.yml
vendored
@@ -27,8 +27,7 @@ jobs:
|
|||||||
github.event.comment.user.login == 'LaserKaspar' ||
|
github.event.comment.user.login == 'LaserKaspar' ||
|
||||||
github.event.comment.user.login == 'sbplat' ||
|
github.event.comment.user.login == 'sbplat' ||
|
||||||
github.event.comment.user.login == 'reecebrowne' ||
|
github.event.comment.user.login == 'reecebrowne' ||
|
||||||
github.event.comment.user.login == 'DarioGii' ||
|
github.event.comment.user.login == 'DarioGii'
|
||||||
github.event.comment.user.login == 'ConnorYoh'
|
|
||||||
)
|
)
|
||||||
outputs:
|
outputs:
|
||||||
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
||||||
@@ -37,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
@@ -103,10 +102,9 @@ jobs:
|
|||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DOCKER_ENABLE_SECURITY: false
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@@ -121,7 +119,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Build and push PR-specific image
|
- name: Build and push PR-specific image
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
30
.github/workflows/PR-Demo-cleanup.yml
vendored
30
.github/workflows/PR-Demo-cleanup.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Cleanup PR deployment
|
- name: Cleanup PR deployment
|
||||||
id: cleanup
|
id: cleanup
|
||||||
run: |
|
run: |
|
||||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
CLEANUP_STATUS=$(ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||||
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
|
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
|
||||||
echo "Found PR directory, proceeding with cleanup..."
|
echo "Found PR directory, proceeding with cleanup..."
|
||||||
|
|
||||||
@@ -57,3 +57,29 @@ jobs:
|
|||||||
echo "NO_CLEANUP_NEEDED"
|
echo "NO_CLEANUP_NEEDED"
|
||||||
fi
|
fi
|
||||||
ENDSSH
|
ENDSSH
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
|
||||||
|
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "cleanup_performed=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Post cleanup notice to PR
|
||||||
|
if: steps.cleanup.outputs.cleanup_performed == 'true'
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { GITHUB_REPOSITORY } = process.env;
|
||||||
|
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||||
|
const prNumber = context.issue.number;
|
||||||
|
|
||||||
|
const commentBody = `## 🧹 Deployment Cleanup\n\n` +
|
||||||
|
`The test deployment for this PR has been cleaned up.`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: repoOwner,
|
||||||
|
repo: repoName,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|||||||
2
.github/workflows/auto-labeler.yml
vendored
2
.github/workflows/auto-labeler.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
54
.github/workflows/build.yml
vendored
54
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.jdk-version }}
|
java-version: ${{ matrix.jdk-version }}
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}
|
||||||
path: |
|
path: |
|
||||||
@@ -58,35 +58,6 @@ jobs:
|
|||||||
build/reports/problems/
|
build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
check-licence:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
|
||||||
with:
|
|
||||||
java-version: "17"
|
|
||||||
distribution: "adopt"
|
|
||||||
|
|
||||||
- name: check the licenses for compatibility
|
|
||||||
run: ./gradlew clean checkLicense
|
|
||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
||||||
with:
|
|
||||||
name: dependencies-without-allowed-license.json
|
|
||||||
path: |
|
|
||||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
|
||||||
retention-days: 3
|
|
||||||
|
|
||||||
docker-compose-tests:
|
docker-compose-tests:
|
||||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
||||||
# (github.event_name == 'pull_request' &&
|
# (github.event_name == 'pull_request' &&
|
||||||
@@ -106,7 +77,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -114,31 +85,30 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up Java 17
|
- name: Set up Java 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "adopt"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||||
|
|
||||||
- name: Install Docker Compose
|
- name: Install Docker Compose
|
||||||
run: |
|
run: |
|
||||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
sudo chmod +x /usr/local/bin/docker-compose
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: 'pip' # caching pip dependencies
|
|
||||||
|
|
||||||
- name: Pip requirements
|
- name: Pip requirements
|
||||||
run: |
|
run: |
|
||||||
pip install --require-hashes -r ./testing/cucumber/requirements.txt
|
pip install --require-hashes -r ./cucumber/requirements.txt
|
||||||
|
|
||||||
- name: Run Docker Compose Tests
|
- name: Run Docker Compose Tests
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./testing/test_webpages.sh
|
chmod +x ./cucumber/test_webpages.sh
|
||||||
chmod +x ./testing/test.sh
|
chmod +x ./test.sh
|
||||||
./testing/test.sh
|
./test.sh
|
||||||
|
|||||||
8
.github/workflows/check_properties.yml
vendored
8
.github/workflows/check_properties.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Fetching PR changed files..."
|
echo "Fetching PR changed files..."
|
||||||
echo "Getting list of changed files from PR..."
|
echo "Getting list of changed files from PR..."
|
||||||
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt # Filter only matching property files
|
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
|
||||||
|
|
||||||
- name: Determine reference file test
|
- name: Determine reference file test
|
||||||
id: determine-file
|
id: determine-file
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
// Filter for relevant files based on the PR changes
|
// Filter for relevant files based on the PR changes
|
||||||
const changedFiles = files
|
const changedFiles = files
|
||||||
.map(file => file.filename)
|
.map(file => file.filename)
|
||||||
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
|
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]+\.properties$/.test(file));
|
||||||
|
|
||||||
console.log("Changed files:", changedFiles);
|
console.log("Changed files:", changedFiles);
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
27
.github/workflows/licenses-update.yml
vendored
27
.github/workflows/licenses-update.yml
vendored
@@ -18,39 +18,30 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Generate GitHub App Token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ vars.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "adopt"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
|
|
||||||
- name: check the licenses for compatibility
|
- name: Run Gradle Command
|
||||||
run: ./gradlew clean checkLicense
|
run: ./gradlew clean generateLicenseReport
|
||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
||||||
with:
|
|
||||||
name: dependencies-without-allowed-license.json
|
|
||||||
path: |
|
|
||||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
|
||||||
retention-days: 3
|
|
||||||
|
|
||||||
- name: Move and Rename License File
|
- name: Move and Rename License File
|
||||||
run: |
|
run: |
|
||||||
@@ -69,7 +60,7 @@ jobs:
|
|||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: "Update 3rd Party Licenses"
|
commit-message: "Update 3rd Party Licenses"
|
||||||
|
|||||||
4
.github/workflows/manage-label.yml
vendored
4
.github/workflows/manage-label.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Run Labeler
|
- name: Run Labeler
|
||||||
uses: crazy-max/ghaction-github-labeler@31674a3852a9074f2086abcf1c53839d466a47e7 # v5.2.0
|
uses: crazy-max/ghaction-github-labeler@b54af0c25861143e7c8813d7cbbf46d2c341680c # v5.1.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
39
.github/workflows/multiOSReleases.yml
vendored
39
.github/workflows/multiOSReleases.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -51,19 +51,19 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "21"
|
java-version: "21"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.12
|
gradle-version: 8.12
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -130,8 +130,8 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
platform: win-
|
platform: win-
|
||||||
- os: macos-latest
|
# - os: macos-latest
|
||||||
platform: mac-
|
# platform: mac-
|
||||||
# - os: ubuntu-latest
|
# - os: ubuntu-latest
|
||||||
# platform: linux-
|
# platform: linux-
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -139,19 +139,19 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "21"
|
java-version: "21"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.12
|
gradle-version: 8.12
|
||||||
|
|
||||||
@@ -168,7 +168,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DOCKER_ENABLE_SECURITY: false
|
||||||
STIRLING_PDF_DESKTOP_UI: true
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
BROWSER_OPEN: true
|
|
||||||
|
|
||||||
# Rename and collect artifacts based on OS
|
# Rename and collect artifacts based on OS
|
||||||
- name: Prepare artifacts
|
- name: Prepare artifacts
|
||||||
@@ -188,7 +187,7 @@ jobs:
|
|||||||
run: ls -R ./binaries
|
run: ls -R ./binaries
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -203,14 +202,14 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
platform: win-
|
platform: win-
|
||||||
- os: macos-latest
|
# - os: macos-latest
|
||||||
platform: mac-
|
# platform: mac-
|
||||||
# - os: ubuntu-latest
|
# - os: ubuntu-latest
|
||||||
# platform: linux-
|
# platform: linux-
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -224,7 +223,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@@ -255,7 +254,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -271,7 +270,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
47
.github/workflows/pre_commit.yml
vendored
47
.github/workflows/pre_commit.yml
vendored
@@ -2,50 +2,31 @@ name: Pre-commit
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * 1"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
|
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
|
||||||
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
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
cache: 'pip' # caching pip dependencies
|
|
||||||
- name: Run Pre-Commit Hooks
|
- name: Run Pre-Commit Hooks
|
||||||
run: |
|
run: |
|
||||||
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
|
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
|
||||||
@@ -53,25 +34,25 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Set up git config
|
- name: Set up git config
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name ${{ steps.generate-token.outputs.app-slug }}[bot]
|
git config --global user.name "github-actions[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.email "github-actions[bot]@users.noreply.github.com"
|
||||||
- name: git add
|
- name: git add
|
||||||
run: |
|
run: |
|
||||||
git add .
|
git add .
|
||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
|
||||||
|
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: ":file_folder: pre-commit"
|
commit-message: "ci: 🤖 format everything with pre-commit"
|
||||||
committer: ${{ steps.committer.outputs.string }}
|
committer: GitHub Action <action@github.com>
|
||||||
author: ${{ steps.committer.outputs.string }}
|
author: GitHub Action <action@github.com>
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: pre-commit
|
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: |
|
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
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
draft: false
|
draft: false
|
||||||
|
|||||||
19
.github/workflows/push-docker.yml
vendored
19
.github/workflows/push-docker.yml
vendored
@@ -18,19 +18,19 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.12
|
gradle-version: 8.12
|
||||||
|
|
||||||
@@ -38,17 +38,16 @@ jobs:
|
|||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DOCKER_ENABLE_SECURITY: false
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.1"
|
cosign-release: "v2.4.1"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@@ -68,7 +67,7 @@ jobs:
|
|||||||
password: ${{ github.token }}
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3.4.0
|
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||||
|
|
||||||
- name: Convert repository owner to lowercase
|
- name: Convert repository owner to lowercase
|
||||||
id: repoowner
|
id: repoowner
|
||||||
@@ -90,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile
|
- name: Build and push main Dockerfile
|
||||||
id: build-push-regular
|
id: build-push-regular
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
@@ -135,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push Dockerfile-ultra-lite
|
- name: Build and push Dockerfile-ultra-lite
|
||||||
id: build-push-lite
|
id: build-push-lite
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
@@ -166,7 +165,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile fat
|
- name: Build and push main Dockerfile fat
|
||||||
id: build-push-fat
|
id: build-push-fat
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
|||||||
16
.github/workflows/releaseArtifacts.yml
vendored
16
.github/workflows/releaseArtifacts.yml
vendored
@@ -23,19 +23,19 @@ jobs:
|
|||||||
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.12
|
gradle-version: 8.12
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
ls -R ./build/launch4j
|
ls -R ./build/launch4j
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: binaries${{ matrix.file_suffix }}
|
name: binaries${{ matrix.file_suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
run: cosign generate-key-pair
|
run: cosign generate-key-pair
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: signed${{ matrix.file_suffix }}
|
name: signed${{ matrix.file_suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@@ -74,6 +74,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
63
.github/workflows/sonarqube.yml
vendored
63
.github/workflows/sonarqube.yml
vendored
@@ -1,63 +0,0 @@
|
|||||||
name: Run Sonarqube
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request_target:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: read
|
|
||||||
actions: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sonarqube:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
|
||||||
|
|
||||||
- name: Build and analyze with Gradle
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
||||||
DOCKER_ENABLE_SECURITY: true
|
|
||||||
STIRLING_PDF_DESKTOP_UI: true
|
|
||||||
run: |
|
|
||||||
./gradlew clean build sonar \
|
|
||||||
-Dsonar.projectKey=Stirling-Tools_Stirling-PDF \
|
|
||||||
-Dsonar.organization=stirling-tools \
|
|
||||||
-Dsonar.host.url=https://sonarcloud.io \
|
|
||||||
-Dsonar.login=${SONAR_TOKEN} \
|
|
||||||
-Dsonar.log.level=DEBUG \
|
|
||||||
--info
|
|
||||||
|
|
||||||
- name: Upload Problems Report on Failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
||||||
with:
|
|
||||||
name: gradle-problems-report
|
|
||||||
path: build/reports/problems/problems-report.html
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
- name: Upload Sonar Logs on Failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
||||||
with:
|
|
||||||
name: sonar-logs
|
|
||||||
path: |
|
|
||||||
.scannerwork/report-task.txt
|
|
||||||
build/sonar/
|
|
||||||
retention-days: 7
|
|
||||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: 30 days stale issues
|
- name: 30 days stale issues
|
||||||
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
|
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
|
|||||||
6
.github/workflows/swagger.yml
vendored
6
.github/workflows/swagger.yml
vendored
@@ -14,19 +14,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||||
|
|
||||||
- name: Generate Swagger documentation
|
- name: Generate Swagger documentation
|
||||||
run: ./gradlew generateOpenApiDocs
|
run: ./gradlew generateOpenApiDocs
|
||||||
|
|||||||
125
.github/workflows/sync_files.yml
vendored
125
.github/workflows/sync_files.yml
vendored
@@ -1,145 +1,62 @@
|
|||||||
name: Sync Files
|
name: Sync Files
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- "build.gradle"
|
- "build.gradle"
|
||||||
- "README.md"
|
|
||||||
- "src/main/resources/messages_*.properties"
|
- "src/main/resources/messages_*.properties"
|
||||||
- "src/main/resources/static/3rdPartyLicenses.json"
|
|
||||||
- "scripts/ignore_translation.toml"
|
- "scripts/ignore_translation.toml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
read_bot_entries:
|
sync-readme:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
permissions:
|
||||||
userName: ${{ steps.get-user-id.outputs.user_name }}
|
contents: write
|
||||||
userEmail: ${{ steps.get-user-id.outputs.user_email }}
|
pull-requests: write
|
||||||
committer: ${{ steps.committer.outputs.committer }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
|
||||||
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@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.GH_APP_ID }}
|
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: 'pip' # caching pip dependencies
|
|
||||||
|
|
||||||
- name: Sync translation property files
|
|
||||||
run: |
|
|
||||||
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 ${{ needs.read_bot_entries.outputs.userName }}
|
|
||||||
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
|
|
||||||
|
|
||||||
- name: Run git add
|
|
||||||
run: |
|
|
||||||
git add src/main/resources/messages_*.properties
|
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||||
|
- name: Sync README
|
||||||
- name: Sync README.md
|
run: python scripts/counter_translation.py
|
||||||
|
- name: Set up git config
|
||||||
run: |
|
run: |
|
||||||
python scripts/counter_translation.py
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
- name: Run git add
|
- name: Run git add
|
||||||
run: |
|
run: |
|
||||||
git add README.md
|
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
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: Update files
|
commit-message: Update files
|
||||||
committer: ${{ needs.read_bot_entries.outputs.committer }}
|
committer: GitHub Action <action@github.com>
|
||||||
author: ${{ needs.read_bot_entries.outputs.committer }}
|
author: GitHub Action <action@github.com>
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: sync_readme
|
branch: sync_readme
|
||||||
title: ":globe_with_meridians: Sync Translations + Update README Progress Table"
|
title: ":memo: Update README: Translation Progress Table"
|
||||||
body: |
|
body: |
|
||||||
### Description of Changes
|
Auto-generated by [create-pull-request][1]
|
||||||
|
|
||||||
This Pull Request was automatically generated to synchronize updates to translation files and documentation. Below are the details of the changes made:
|
|
||||||
|
|
||||||
#### **1. Synchronization of Translation Files**
|
|
||||||
- Updated translation files (`messages_*.properties`) to reflect changes in the reference file `messages_en_GB.properties`.
|
|
||||||
- Ensured consistency and synchronization across all supported language files.
|
|
||||||
- Highlighted any missing or incomplete translations.
|
|
||||||
|
|
||||||
#### **2. Update README.md**
|
|
||||||
- Generated the translation progress table in `README.md`.
|
|
||||||
- Added a summary of the current translation status for all supported languages.
|
|
||||||
- Included up-to-date statistics on translation coverage.
|
|
||||||
|
|
||||||
#### **Why these changes are necessary**
|
|
||||||
- Keeps translation files aligned with the latest reference updates.
|
|
||||||
- Ensures the documentation reflects the current translation progress.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Auto-generated by [create-pull-request][1].
|
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
draft: false
|
draft: false
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
labels: github-actions
|
labels: Documentation,Translation,github-actions
|
||||||
sign-commits: true
|
sign-commits: true
|
||||||
add-paths: |
|
|
||||||
README.md
|
|
||||||
src/main/resources/messages_*.properties
|
|
||||||
|
|||||||
16
.github/workflows/testdriver.yml
vendored
16
.github/workflows/testdriver.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
DOCKER_ENABLE_SECURITY: false
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Build and push test image
|
- name: Build and push test image
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
@@ -105,14 +105,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Run TestDriver.ai
|
- name: Run TestDriver.ai
|
||||||
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
uses: testdriverai/action@47e87c5d50beeeb3da624b2d9b5c1391269d6d22 #1.0.0
|
||||||
with:
|
with:
|
||||||
key: ${{secrets.TESTDRIVER_API_KEY}}
|
key: ${{secrets.TESTDRIVER_API_KEY}}
|
||||||
prerun: |
|
prerun: |
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
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-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
|
Start-Sleep -Seconds 20
|
||||||
prompt: |
|
prompt: |
|
||||||
1. /run testing/testdriver/test.yml
|
1. /run testdriver/test.yml
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
FORCE_COLOR: "3"
|
FORCE_COLOR: "3"
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
72
.github/workflows/update-translations.yml
vendored
Normal file
72
.github/workflows/update-translations.yml
vendored
Normal 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@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
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.12"
|
||||||
|
|
||||||
|
- 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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,11 +21,9 @@ pipeline/finishedFolders/
|
|||||||
customFiles/
|
customFiles/
|
||||||
configs/
|
configs/
|
||||||
watchedFolders/
|
watchedFolders/
|
||||||
clientWebUI/
|
|
||||||
!cucumber/
|
!cucumber/
|
||||||
!cucumber/exampleFiles/
|
!cucumber/exampleFiles/
|
||||||
!cucumber/exampleFiles/example_html.zip
|
!cucumber/exampleFiles/example_html.zip
|
||||||
exampleYmlFiles/stirling/
|
|
||||||
|
|
||||||
# Gradle
|
# Gradle
|
||||||
.gradle
|
.gradle
|
||||||
@@ -140,7 +138,6 @@ venv.bak/
|
|||||||
# VS Code
|
# VS Code
|
||||||
/.vscode/**/*
|
/.vscode/**/*
|
||||||
!/.vscode/settings.json
|
!/.vscode/settings.json
|
||||||
!/.vscode/extensions.json
|
|
||||||
|
|
||||||
# IntelliJ IDEA
|
# IntelliJ IDEA
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ repos:
|
|||||||
args:
|
args:
|
||||||
- --fix
|
- --fix
|
||||||
- --line-length=127
|
- --line-length=127
|
||||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||||
exclude: (split_photos.py)
|
exclude: (split_photos.py)
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||||
exclude: (split_photos.py)
|
exclude: (split_photos.py)
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.3.0
|
rev: v2.3.0
|
||||||
@@ -19,18 +19,39 @@ repos:
|
|||||||
- --ignore-words-list=
|
- --ignore-words-list=
|
||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
files: \.(html|css|js|py|md)$
|
files: \.(properties|html|css|js|py|md)$
|
||||||
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.22.0
|
rev: v8.22.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
|
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||||
|
rev: 3.0.0
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
files: ^.*(\.bash|\.sh|\.ksh|\.zsh)$
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
files: ^.*(\.js|\.java|\.py|\.yml)$
|
||||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
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)$
|
||||||
23
.vscode/extensions.json
vendored
23
.vscode/extensions.json
vendored
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality
|
|
||||||
"josevseb.google-java-format-for-vs-code", // Google Java code formatter to follow the Google Java Style Guide
|
|
||||||
"ms-python.black-formatter", // Python code formatter using Black
|
|
||||||
"ms-python.flake8", // Flake8 linter for Python to enforce code quality
|
|
||||||
"ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support
|
|
||||||
// "ms-vscode-remote.remote-containers", // Support for remote development with containers (Docker, Dev Containers)
|
|
||||||
// "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
|
|
||||||
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development
|
|
||||||
"redhat.java", // Java support by Red Hat with IntelliSense, debugging, and code navigation
|
|
||||||
"streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos
|
|
||||||
"vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware
|
|
||||||
"vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development
|
|
||||||
"vscjava.vscode-gradle", // Gradle extension for build and automation support
|
|
||||||
"vscjava.vscode-java-debug", // Debugging support for Java projects
|
|
||||||
"vscjava.vscode-java-dependency", // Java dependency management within VS Code
|
|
||||||
"vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code
|
|
||||||
"vscjava.vscode-java-test", // Java test framework for running and debugging tests in VS Code
|
|
||||||
"vscjava.vscode-spring-boot-dashboard", // Spring Boot dashboard for managing and visualizing Spring Boot applications
|
|
||||||
"vscjava.vscode-spring-initializr" // Support for Spring Initializr to create new Spring projects
|
|
||||||
]
|
|
||||||
}
|
|
||||||
121
.vscode/settings.json
vendored
121
.vscode/settings.json
vendored
@@ -2,147 +2,54 @@
|
|||||||
"java.compile.nullAnalysis.mode": "automatic",
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
"files.eol": "auto",
|
"files.eol": "auto",
|
||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"black-formatter.args": [
|
"black-formatter.args": ["--line-length", "127"],
|
||||||
"--line-length",
|
"flake8.args": ["--max-line-length", "127"],
|
||||||
"127"
|
"pylint.args": ["max-line-length", "127"],
|
||||||
],
|
|
||||||
"flake8.args": [
|
|
||||||
"--max-line-length",
|
|
||||||
"127"
|
|
||||||
],
|
|
||||||
"[java]": {
|
"[java]": {
|
||||||
"editor.tabSize": 4,
|
"editor.tabSize": 4,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127]
|
||||||
127
|
|
||||||
],
|
|
||||||
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
|
|
||||||
},
|
},
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127]
|
||||||
127
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"[gradle-build]": {
|
"[gradle-build]": {
|
||||||
"editor.tabSize": 4,
|
"editor.tabSize": 4,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127]
|
||||||
127
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"[gradle]": {
|
"[gradle]": {
|
||||||
"editor.tabSize": 4,
|
"editor.tabSize": 4,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127]
|
||||||
127
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127],
|
||||||
127
|
|
||||||
],
|
|
||||||
"files.trimFinalNewlines": false,
|
"files.trimFinalNewlines": false,
|
||||||
"files.insertFinalNewline": false
|
"files.insertFinalNewline": false
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.rulers": [
|
"editor.rulers": [127]
|
||||||
127
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"[yaml]": {
|
"[yaml]": {
|
||||||
"files.trimFinalNewlines": false,
|
"files.trimFinalNewlines": false,
|
||||||
"files.insertFinalNewline": false
|
"files.insertFinalNewline": false
|
||||||
},
|
},
|
||||||
|
"diffEditor.maxComputationTime": 0,
|
||||||
|
"editor.wordSegmenterLocales": null,
|
||||||
|
"editor.guides.bracketPairs": "active",
|
||||||
|
"editor.guides.bracketPairsHorizontal": "active",
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.trimFinalNewlines": true,
|
"files.trimFinalNewlines": true,
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"files.autoSave": "onFocusChange",
|
|
||||||
"files.autoSaveWhenNoErrors": true,
|
|
||||||
"diffEditor.maxComputationTime": 0,
|
|
||||||
"editor.wordSegmenterLocales": "",
|
|
||||||
"editor.guides.bracketPairs": "active",
|
|
||||||
"editor.guides.bracketPairsHorizontal": "active",
|
|
||||||
"editor.indentSize": "tabSize",
|
"editor.indentSize": "tabSize",
|
||||||
"editor.stickyScroll.enabled": false,
|
"editor.stickyScroll.enabled": false,
|
||||||
"editor.minimap.enabled": false,
|
"editor.minimap.enabled": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.insertSpaces": true,
|
|
||||||
"java.format.enabled": true,
|
|
||||||
"java.format.settings.profile": "GoogleStyle",
|
|
||||||
"java.format.settings.google.version": "1.25.2",
|
|
||||||
"java.format.settings.google.mode": "jar-file",
|
"java.format.settings.google.mode": "jar-file",
|
||||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
"java.format.settings.google.extra": "--aosp --skip-sorting-imports"
|
||||||
// (DE) Aktiviert Kommentare im Java-Format.
|
|
||||||
// (EN) Enables comments in Java formatting.
|
|
||||||
// "java.format.comments.enabled": true,
|
|
||||||
// (DE) Generiert automatisch Kommentare im Code.
|
|
||||||
// (EN) Automatically generates comments in code.
|
|
||||||
// "java.codeGeneration.generateComments": true,
|
|
||||||
// https://github.com/redhat-developer/vscode-java/blob/master/document/_java.learnMoreAboutCleanUps.md#java-clean-ups
|
|
||||||
"java.saveActions.cleanup": true,
|
|
||||||
"java.cleanup.actions": [
|
|
||||||
"invertEquals", // Inverts calls to Object.equals(Object) and String.equalsIgnoreCase(String) to avoid useless null pointer exception.
|
|
||||||
"instanceofPatternMatch" // Replaces instanceof checks with pattern matching.
|
|
||||||
],
|
|
||||||
// (DE) Aktiviert die Code-Vervollständigung für Java.
|
|
||||||
// (EN) Enables code completion for Java.
|
|
||||||
"java.completion.engine": "dom",
|
|
||||||
"java.completion.enabled": true,
|
|
||||||
"java.completion.importOrder": [
|
|
||||||
"java",
|
|
||||||
"javax",
|
|
||||||
"org",
|
|
||||||
"com",
|
|
||||||
"net",
|
|
||||||
"io",
|
|
||||||
"jakarta",
|
|
||||||
"lombok",
|
|
||||||
"me",
|
|
||||||
"stirling",
|
|
||||||
],
|
|
||||||
"java.project.resourceFilters": [
|
|
||||||
".devcontainer/",
|
|
||||||
".git/",
|
|
||||||
".github/",
|
|
||||||
".gradle/",
|
|
||||||
".venv/",
|
|
||||||
".venv*/",
|
|
||||||
".vscode/",
|
|
||||||
"bin/",
|
|
||||||
"build/",
|
|
||||||
"configs/",
|
|
||||||
"customFiles/",
|
|
||||||
"docs/",
|
|
||||||
"exampleYmlFiles",
|
|
||||||
"gradle/",
|
|
||||||
"images/",
|
|
||||||
"logs/",
|
|
||||||
"pipeline/",
|
|
||||||
"scripts/",
|
|
||||||
"testings/",
|
|
||||||
".git-blame-ignore-revs",
|
|
||||||
".gitattributes",
|
|
||||||
".gitignore",
|
|
||||||
".pre-commit-config.yaml",
|
|
||||||
],
|
|
||||||
// Enables signature help in Java.
|
|
||||||
"java.signatureHelp.enabled": true,
|
|
||||||
// Enables detailed signature help descriptions.
|
|
||||||
"java.signatureHelp.description.enabled": true,
|
|
||||||
// Downloads sources for Maven dependencies.
|
|
||||||
"java.maven.downloadSources": true,
|
|
||||||
// Enables Gradle project import.
|
|
||||||
"java.import.gradle.enabled": true,
|
|
||||||
// Downloads sources for Eclipse projects.
|
|
||||||
"java.eclipse.downloadSources": true,
|
|
||||||
// Enables import of the Gradle wrapper.
|
|
||||||
"java.import.gradle.wrapper.enabled": true,
|
|
||||||
"spring.initializr.defaultLanguage": "Java",
|
|
||||||
"spring.initializr.defaultGroupId": "stirling.software.SPDF",
|
|
||||||
"spring.initializr.defaultArtifactId": "SPDF",
|
|
||||||
"cSpell.enabled": false,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,16 +39,6 @@ Stirling-PDF is built using:
|
|||||||
2. Install Docker and JDK17 if not already installed.
|
2. Install Docker and JDK17 if not already installed.
|
||||||
|
|
||||||
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
|
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
|
||||||
1. Only VSCode
|
|
||||||
1. Open VS Code.
|
|
||||||
2. When prompted, install the recommended extensions.
|
|
||||||
3. Alternatively, open the command palette (`Ctrl + Shift + P` or `Cmd + Shift + P` on macOS) and run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
Extensions: Show Recommended Extensions
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Install the required extensions from the list.
|
|
||||||
|
|
||||||
4. Lombok Setup
|
4. Lombok Setup
|
||||||
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
||||||
|
|||||||
79
Dockerfile
79
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
@@ -25,66 +25,49 @@ LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, conver
|
|||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||||
-XX:MaxRAMPercentage=75 \
|
|
||||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
|
||||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
|
||||||
-XX:G1PeriodicGCInterval=10000 \
|
|
||||||
-XX:+UseStringDeduplication \
|
|
||||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022 \
|
UMASK=022
|
||||||
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
|
||||||
UNO_PATH=/usr/lib/libreoffice/program \
|
|
||||||
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
|
|
||||||
|
|
||||||
|
|
||||||
# JDK for app
|
# JDK for app
|
||||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
apk upgrade --no-cache -a && \
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
qpdf \
|
qpdf \
|
||||||
shadow \
|
shadow \
|
||||||
su-exec \
|
su-exec \
|
||||||
openssl \
|
openssl \
|
||||||
openssl-dev \
|
openssl-dev \
|
||||||
openjdk21-jre \
|
openjdk21-jre \
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
gcompat \
|
libreoffice \
|
||||||
libc6-compat \
|
# pdftohtml
|
||||||
libreoffice \
|
poppler-utils \
|
||||||
# pdftohtml
|
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||||
poppler-utils \
|
tesseract-ocr-data-eng \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced features)
|
# CV
|
||||||
tesseract-ocr-data-eng \
|
py3-opencv \
|
||||||
# CV
|
# python3/pip
|
||||||
py3-opencv \
|
python3 \
|
||||||
python3 \
|
py3-pip && \
|
||||||
py3-pip \
|
# uno unoconv and HTML
|
||||||
py3-pillow@testing \
|
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||||
py3-pdf2image@testing && \
|
|
||||||
python3 -m venv /opt/venv && \
|
|
||||||
export PATH="/opt/venv/bin:$PATH" && \
|
|
||||||
pip install --upgrade pip && \
|
|
||||||
pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
|
||||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
|
||||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
|
||||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
|
||||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||||
fc-cache -f -v && \
|
fc-cache -f -v && \
|
||||||
chmod +x /scripts/* && \
|
chmod +x /scripts/* && \
|
||||||
chmod +x /scripts/init.sh && \
|
chmod +x /scripts/init.sh && \
|
||||||
# User permissions
|
# User permissions
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
@@ -93,4 +76,4 @@ EXPOSE 8080/tcp
|
|||||||
|
|
||||||
# Set user and run command
|
# Set user and run command
|
||||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ COPY . .
|
|||||||
|
|
||||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
# Build the application with DOCKER_ENABLE_SECURITY=false
|
||||||
RUN DOCKER_ENABLE_SECURITY=true \
|
RUN DOCKER_ENABLE_SECURITY=true \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
./gradlew clean build
|
||||||
./gradlew clean build
|
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
@@ -26,75 +25,58 @@ ARG VERSION_TAG
|
|||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||||
-XX:MaxRAMPercentage=75 \
|
|
||||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
|
||||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
|
||||||
-XX:G1PeriodicGCInterval=10000 \
|
|
||||||
-XX:+UseStringDeduplication \
|
|
||||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022 \
|
UMASK=022 \
|
||||||
FAT_DOCKER=true \
|
FAT_DOCKER=true \
|
||||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||||
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
|
||||||
UNO_PATH=/usr/lib/libreoffice/program \
|
|
||||||
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
|
|
||||||
|
|
||||||
|
|
||||||
# JDK for app
|
# JDK for app
|
||||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
apk upgrade --no-cache -a && \
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
shadow \
|
shadow \
|
||||||
su-exec \
|
su-exec \
|
||||||
openssl \
|
openssl \
|
||||||
openssl-dev \
|
openssl-dev \
|
||||||
openjdk21-jre \
|
openjdk21-jre \
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
gcompat \
|
libreoffice \
|
||||||
libc6-compat \
|
# pdftohtml
|
||||||
libreoffice \
|
poppler-utils \
|
||||||
# pdftohtml
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
poppler-utils \
|
qpdf \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
tesseract-ocr-data-eng \
|
||||||
qpdf \
|
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||||
tesseract-ocr-data-eng \
|
# CV
|
||||||
|
py3-opencv \
|
||||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \
|
# python3/pip
|
||||||
# CV
|
python3 \
|
||||||
py3-opencv \
|
py3-pip && \
|
||||||
python3 \
|
# uno unoconv and HTML
|
||||||
py3-pip \
|
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||||
py3-pillow@testing \
|
|
||||||
py3-pdf2image@testing && \
|
|
||||||
python3 -m venv /opt/venv && \
|
|
||||||
export PATH="/opt/venv/bin:$PATH" && \
|
|
||||||
pip install --upgrade pip && \
|
|
||||||
pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
|
||||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
|
||||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
|
||||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
|
||||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||||
fc-cache -f -v && \
|
fc-cache -f -v && \
|
||||||
chmod +x /scripts/* && \
|
chmod +x /scripts/* && \
|
||||||
chmod +x /scripts/init.sh && \
|
chmod +x /scripts/init.sh && \
|
||||||
# User permissions
|
# User permissions
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
# Set user and run command
|
# Set user and run command
|
||||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@@ -7,13 +7,7 @@ ARG VERSION_TAG
|
|||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||||
-XX:MaxRAMPercentage=75 \
|
|
||||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
|
||||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
|
||||||
-XX:G1PeriodicGCInterval=10000 \
|
|
||||||
-XX:+UseStringDeduplication \
|
|
||||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022
|
UMASK=022
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ Fork Stirling-PDF and create a new branch out of `main`.
|
|||||||
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
|
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
|
||||||
|
|
||||||
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
|
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
|
||||||
|
- Add a flag SVG file to: [flags directory](https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags)
|
||||||
|
|
||||||
|
Any SVG flags are fine; most of the current ones were sourced from [here](https://flagicons.lipis.dev/). If your language isn't represented by a flag, choose a similar one, such as Saudi Arabia's flag for Arabic.
|
||||||
|
|
||||||
For example, to add Polish, you would add:
|
For example, to add Polish, you would add:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div th:replace="~{fragments/languageEntry :: languageEntry ('pl_PL', 'Polski')}" ></div>
|
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
|
||||||
|
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||||
|
</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `data-bs-language-code` is the code used to reference the file in the next step.
|
The `data-bs-language-code` is the code used to reference the file in the next step.
|
||||||
@@ -56,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`).
|
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
|
||||||
|
|
||||||
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.
|
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.
|
||||||
|
|
||||||
### Use this code to perform a local check
|
|
||||||
|
|
||||||
#### Windows command
|
|
||||||
|
|
||||||
```ps
|
|
||||||
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_pl_PL.properties
|
|
||||||
|
|
||||||
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --check-file src\main\resources\messages_pl_PL.properties
|
|
||||||
```
|
|
||||||
|
|||||||
70
README.md
70
README.md
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
[](https://discord.gg/HYmhKj45pU)
|
[](https://discord.gg/HYmhKj45pU)
|
||||||
|
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||||
[](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
|
[](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
|
||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
|
|
||||||
@@ -112,50 +113,49 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
|
|||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
Stirling-PDF currently supports 39 languages!
|
Stirling-PDF currently supports 38 languages!
|
||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Stirling PDF Enterprise
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
{
|
|
||||||
"allowedLicenses": [
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "BSD License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The BSD License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "BSD-2-Clause"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "BSD 2-Clause License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The 2-Clause BSD License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "BSD-3-Clause"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The BSD 3-Clause License (BSD3)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "BSD-4 License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "MIT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "MIT License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The MIT License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.github.jai-imageio:jai-imageio-core",
|
|
||||||
"moduleLicense": "LICENSE.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.github.jai-imageio:jai-imageio-jpeg2000",
|
|
||||||
"moduleLicense": "LICENSE-JJ2000.txt, LICENSE-Sun.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache 2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache-2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache-2.0 License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache License 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache License Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The Apache License, Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.nimbusds:oauth2-oidc-sdk",
|
|
||||||
"moduleLicense": "\"Apache License, version 2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.html\""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "MPL 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "UnboundID SCIM2 SDK Free Use License"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "GPL2 w/ CPE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "GPLv2+CE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.martiansoftware:jsap",
|
|
||||||
"moduleLicense": "LGPL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.hibernate.orm:hibernate-core",
|
|
||||||
"moduleLicense": "GNU Library General Public License v2.1 or later"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Eclipse Public License - v 1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Eclipse Public License v. 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Eclipse Public License - v 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Eclipse Public License - Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Eclipse Public License, Version 2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Ubuntu Font Licence 1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Bouncy Castle Licence"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "Public Domain, per Creative Commons CC0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": ".*",
|
|
||||||
"moduleLicense": "The W3C License"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
157
build.gradle
157
build.gradle
@@ -1,31 +1,34 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "org.springframework.boot" version "3.4.3"
|
id "org.springframework.boot" version "3.4.1"
|
||||||
id "io.spring.dependency-management" version "1.1.7"
|
id "io.spring.dependency-management" version "1.1.7"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
id "edu.sc.seis.launch4j" version "3.0.6"
|
id "edu.sc.seis.launch4j" version "3.0.6"
|
||||||
id "com.diffplug.spotless" version "7.0.2"
|
id "com.diffplug.spotless" version "7.0.1"
|
||||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
id("org.panteleyev.jpackageplugin") version "1.6.1"
|
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||||
id "org.sonarqube" version "6.0.1.5171"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.4.3"
|
springBootVersion = "3.4.1"
|
||||||
pdfboxVersion = "3.0.4"
|
pdfboxVersion = "3.0.3"
|
||||||
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.36"
|
lombokVersion = "1.18.36"
|
||||||
bouncycastleVersion = "1.80"
|
bouncycastleVersion = "1.79"
|
||||||
springSecuritySamlVersion = "6.4.3"
|
springSecuritySamlVersion = "6.4.2"
|
||||||
openSamlVersion = "4.3.2"
|
openSamlVersion = "4.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.43.1"
|
version = "0.37.0"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -34,13 +37,14 @@ java {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = "https://build.shibboleth.net/maven/releases" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
maven { url "https://build.shibboleth.net/maven/releases" }
|
||||||
|
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
renderers = [new JsonReportRenderer()]
|
renderers = [new JsonReportRenderer()]
|
||||||
allowedLicensesFile = new File("$projectDir/allowed-licenses.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -64,7 +68,7 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||||
exclude "stirling/software/SPDF/UI/impl/**"
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -109,15 +113,18 @@ def getMacVersion(String version) {
|
|||||||
|
|
||||||
jpackage {
|
jpackage {
|
||||||
input = "build/libs"
|
input = "build/libs"
|
||||||
destination = "${projectDir}/build/jpackage"
|
|
||||||
mainJar = "Stirling-PDF-${project.version}.jar"
|
|
||||||
appName = "Stirling-PDF"
|
appName = "Stirling-PDF"
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
vendor = "Stirling-Software"
|
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"
|
icon = "src/main/resources/static/favicon.ico"
|
||||||
verbose = true
|
|
||||||
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
|
||||||
|
|
||||||
// JVM Options
|
// JVM Options
|
||||||
javaOptions = [
|
javaOptions = [
|
||||||
@@ -125,21 +132,23 @@ jpackage {
|
|||||||
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
||||||
"-Djava.awt.headless=false",
|
"-Djava.awt.headless=false",
|
||||||
"-Dapple.awt.UIElement=true",
|
"-Dapple.awt.UIElement=true",
|
||||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
||||||
"--add-opens=java.desktop/java.awt.event=ALL-UNNAMED",
|
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
|
||||||
"--add-opens=java.desktop/sun.awt=ALL-UNNAMED",
|
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
|
||||||
"--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED",
|
|
||||||
"--add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED",
|
|
||||||
"--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED",
|
|
||||||
"--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
destination = "${projectDir}/build/jpackage"
|
||||||
|
|
||||||
// Windows-specific configuration
|
// Windows-specific configuration
|
||||||
windows {
|
windows {
|
||||||
launcherAsService = false
|
launcherAsService = false
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
|
|
||||||
winConsole = false
|
winConsole = false
|
||||||
winMenu = true // Creates start menu entry
|
winMenu = true // Creates start menu entry
|
||||||
winShortcut = true // Creates desktop shortcut
|
winShortcut = true // Creates desktop shortcut
|
||||||
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
||||||
@@ -155,7 +164,7 @@ jpackage {
|
|||||||
|
|
||||||
// macOS-specific configuration
|
// macOS-specific configuration
|
||||||
mac {
|
mac {
|
||||||
appVersion = getMacVersion(project.version.toString())
|
appVersion = getMacVersion(project.version.toString())
|
||||||
icon = "src/main/resources/static/favicon.icns"
|
icon = "src/main/resources/static/favicon.icns"
|
||||||
type = "dmg"
|
type = "dmg"
|
||||||
macPackageIdentifier = "com.stirling.software.pdf"
|
macPackageIdentifier = "com.stirling.software.pdf"
|
||||||
@@ -179,7 +188,7 @@ jpackage {
|
|||||||
|
|
||||||
// Linux-specific configuration
|
// Linux-specific configuration
|
||||||
linux {
|
linux {
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
icon = "src/main/resources/static/favicon.png"
|
icon = "src/main/resources/static/favicon.png"
|
||||||
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||||
|
|
||||||
@@ -227,9 +236,9 @@ launch4j {
|
|||||||
outfile="Stirling-PDF.exe"
|
outfile="Stirling-PDF.exe"
|
||||||
|
|
||||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
headerType = "gui"
|
headerType = "gui"
|
||||||
} else {
|
} else {
|
||||||
headerType = "console"
|
headerType = "console"
|
||||||
}
|
}
|
||||||
jarTask = tasks.bootJar
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
@@ -237,11 +246,13 @@ launch4j {
|
|||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
|
|
||||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
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 {
|
} else {
|
||||||
variables=["BROWSER_OPEN=true"]
|
variables=["BROWSER_OPEN=true"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
@@ -260,25 +271,14 @@ spotless {
|
|||||||
|
|
||||||
googleJavaFormat("1.25.2").aosp().reorderImports(false)
|
googleJavaFormat("1.25.2").aosp().reorderImports(false)
|
||||||
|
|
||||||
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
importOrder("java", "javax", "org", "com", "net", "io")
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
leadingTabsToSpaces()
|
indentWithSpaces()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sonar {
|
|
||||||
properties {
|
|
||||||
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
|
|
||||||
property "sonar.organization", "stirling-tools"
|
|
||||||
|
|
||||||
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
|
||||||
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
|
||||||
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//gradleLint {
|
//gradleLint {
|
||||||
// rules=['unused-dependency']
|
// rules=['unused-dependency']
|
||||||
// }
|
// }
|
||||||
@@ -293,35 +293,26 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
//tmp for security bumps
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||||
implementation 'ch.qos.logback:logback-core:1.5.17'
|
implementation "me.friwi:jcefmaven:127.3.1"
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.17'
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
|
|
||||||
// Exclude vulnerable BouncyCastle version used in tableau
|
|
||||||
configurations.all {
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
|
||||||
implementation "me.friwi:jcefmaven:132.3.1"
|
|
||||||
implementation "org.openjfx:javafx-controls:21"
|
|
||||||
implementation "org.openjfx:javafx-swing:21"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.2.3"
|
implementation "org.springframework:spring-webmvc:6.2.1"
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.1")
|
implementation("io.github.pixee:java-security-toolkit:1.2.1")
|
||||||
|
|
||||||
|
// implementation "org.yaml:snakeyaml:2.2"
|
||||||
|
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
|
||||||
|
|
||||||
// Exclude Tomcat and include Jetty
|
// Exclude Tomcat and include Jetty
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
||||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||||
implementation 'com.posthog.java:posthog:1.2.0'
|
implementation 'com.posthog.java:posthog:1.1.1'
|
||||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||||
|
|
||||||
|
|
||||||
@@ -331,24 +322,24 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.session:spring-session-core:3.4.2"
|
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||||
implementation "org.springframework:spring-jdbc:6.2.3"
|
implementation "org.springframework:spring-jdbc:6.2.1"
|
||||||
|
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
// Don't upgrade h2database
|
// Don't upgrade h2database
|
||||||
runtimeOnly "com.h2database:h2:2.3.232"
|
runtimeOnly "com.h2database:h2:2.3.232"
|
||||||
runtimeOnly "org.postgresql:postgresql:42.7.5"
|
runtimeOnly "org.postgresql:postgresql:42.7.4"
|
||||||
constraints {
|
constraints {
|
||||||
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
||||||
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
||||||
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
||||||
}
|
}
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
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'
|
implementation 'com.coveo:saml-client:5.0.0'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
implementation 'org.snakeyaml:snakeyaml-engine:2.9'
|
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||||
|
|
||||||
@@ -388,8 +379,6 @@ dependencies {
|
|||||||
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion") {
|
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion") {
|
||||||
exclude group: "commons-logging", module: "commons-logging"
|
exclude group: "commons-logging", module: "commons-logging"
|
||||||
}
|
}
|
||||||
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
|
|
||||||
|
|
||||||
|
|
||||||
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion") {
|
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion") {
|
||||||
exclude group: "commons-logging", module: "commons-logging"
|
exclude group: "commons-logging", module: "commons-logging"
|
||||||
@@ -407,7 +396,7 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||||
implementation "io.micrometer:micrometer-core:1.14.4"
|
implementation "io.micrometer:micrometer-core:1.14.3"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.24.0"
|
implementation "org.commonmark:commonmark:0.24.0"
|
||||||
@@ -416,8 +405,8 @@ dependencies {
|
|||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|
||||||
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
implementation 'org.jsoup:jsoup:1.18.3'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
@@ -441,13 +430,13 @@ task writeVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swaggerhubUpload {
|
swaggerhubUpload {
|
||||||
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
|
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||||
api = "Stirling-PDF" // The name of your API on SwaggerHub
|
api "Stirling-PDF" // The name of your API on SwaggerHub
|
||||||
owner = "Frooodle" // Your SwaggerHub username (or organization name)
|
owner "Frooodle" // Your SwaggerHub username (or organization name)
|
||||||
version = project.version // The version of your API
|
version project.version // The version of your API
|
||||||
inputFile = "./SwaggerDoc.json" // The path to your Swagger docs
|
inputFile "./SwaggerDoc.json" // The path to your Swagger docs
|
||||||
token = "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
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
|
oas "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|||||||
@@ -204,27 +204,4 @@ Feature: API Validation
|
|||||||
Then the response status code should be 200
|
Then the response status code should be 200
|
||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
And the response file should have extension ".pdf"
|
And the response file should have extension ".pdf"
|
||||||
|
|
||||||
@markdown @positive
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
@positive @pdftocsv
|
|
||||||
Scenario: Convert PDF with tables to CSV format
|
|
||||||
Given I use an example file at "exampleFiles/tables.pdf" as parameter "fileInput"
|
|
||||||
And the request data includes
|
|
||||||
| parameter | value |
|
|
||||||
| outputFormat | csv |
|
|
||||||
| pageNumbers | all |
|
|
||||||
When I send the API request to the endpoint "/api/v1/convert/pdf/csv"
|
|
||||||
Then the response status code should be 200
|
|
||||||
And the response file should have size greater than 200
|
|
||||||
And the response file should have extension ".zip"
|
|
||||||
And the response ZIP should contain 3 files
|
|
||||||
|
|
||||||
@@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
# Function to check a single webpage
|
# Function to check a single webpage
|
||||||
check_webpage() {
|
check_webpage() {
|
||||||
local url=$(echo "$1" | tr -d '\r') # Remove carriage returns
|
local url=$1
|
||||||
local base_url=$(echo "$2" | tr -d '\r')
|
local base_url=${2:-"http://localhost:8080"}
|
||||||
local full_url="${base_url}${url}"
|
local full_url="${base_url}${url}"
|
||||||
local timeout=10
|
local timeout=10
|
||||||
|
|
||||||
echo -n "Testing $full_url ... "
|
echo -n "Testing $full_url ... "
|
||||||
|
|
||||||
# Use curl to fetch the page with timeout
|
# Use curl to fetch the page with timeout
|
||||||
response=$(curl -s -w "\n%{http_code}" --max-time $timeout "$full_url")
|
response=$(curl -s -w "\n%{http_code}" --max-time $timeout "$full_url")
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "FAILED - Connection error or timeout $full_url "
|
echo "FAILED - Connection error or timeout"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ check_webpage() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if response contains HTML
|
# Check if response contains HTML
|
||||||
if ! printf '%s' "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then
|
if ! echo "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then
|
||||||
echo "FAILED - Response is not HTML"
|
echo "FAILED - Response is not HTML"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -45,12 +46,11 @@ test_all_urls() {
|
|||||||
|
|
||||||
echo "Starting webpage tests..."
|
echo "Starting webpage tests..."
|
||||||
echo "Base URL: $base_url"
|
echo "Base URL: $base_url"
|
||||||
echo "Number of lines: $(wc -l < "$url_file")"
|
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
|
|
||||||
while IFS= read -r url || [ -n "$url" ]; do
|
while IFS= read -r url || [ -n "$url" ]; do
|
||||||
# Skip empty lines and comments
|
# Skip empty lines
|
||||||
[[ -z "$url" || "$url" =~ ^#.*$ ]] && continue
|
[ -z "$url" ] && continue
|
||||||
|
|
||||||
((total_count++))
|
((total_count++))
|
||||||
if ! check_webpage "$url" "$base_url"; then
|
if ! check_webpage "$url" "$base_url"; then
|
||||||
@@ -60,7 +60,7 @@ test_all_urls() {
|
|||||||
|
|
||||||
local end_time=$(date +%s)
|
local end_time=$(date +%s)
|
||||||
local duration=$((end_time - start_time))
|
local duration=$((end_time - start_time))
|
||||||
|
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
echo "Test Summary:"
|
echo "Test Summary:"
|
||||||
echo "Total tests: $total_count"
|
echo "Total tests: $total_count"
|
||||||
@@ -71,44 +71,18 @@ test_all_urls() {
|
|||||||
return $failed_count
|
return $failed_count
|
||||||
}
|
}
|
||||||
|
|
||||||
# Print usage information
|
|
||||||
usage() {
|
|
||||||
echo "Usage: $0 [-f url_file] [-b base_url]"
|
|
||||||
echo "Options:"
|
|
||||||
echo " -f url_file Path to file containing URLs to test (required)"
|
|
||||||
echo " -b base_url Base URL to prepend to test URLs (default: http://localhost:8080)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
main() {
|
main() {
|
||||||
local url_file=""
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
local base_url="http://localhost:8080"
|
local url_file="${script_dir}/webpage_urls.txt"
|
||||||
|
|
||||||
# Parse command line options
|
|
||||||
while getopts ":f:b:h" opt; do
|
|
||||||
case $opt in
|
|
||||||
f) url_file="$OPTARG" ;;
|
|
||||||
b) base_url="$OPTARG" ;;
|
|
||||||
h) usage ;;
|
|
||||||
\?) echo "Invalid option -$OPTARG" >&2; usage ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check if URL file is provided
|
|
||||||
if [ -z "$url_file" ]; then
|
|
||||||
echo "Error: URL file is required"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if URL file exists
|
|
||||||
if [ ! -f "$url_file" ]; then
|
if [ ! -f "$url_file" ]; then
|
||||||
echo "Error: URL list file not found: $url_file"
|
echo "Error: URL list file not found: $url_file"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run tests using the URL list
|
# Run tests using the URL list
|
||||||
if test_all_urls "$url_file" "$base_url"; then
|
if test_all_urls "$url_file"; then
|
||||||
echo "All webpage tests passed!"
|
echo "All webpage tests passed!"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
/extract-page
|
/extract-page
|
||||||
/pdf-to-single-page
|
/pdf-to-single-page
|
||||||
/img-to-pdf
|
/img-to-pdf
|
||||||
|
/markdown-to-pdf
|
||||||
/pdf-to-img
|
/pdf-to-img
|
||||||
/pdf-to-text
|
/pdf-to-text
|
||||||
/pdf-to-csv
|
/pdf-to-csv
|
||||||
BIN
docs/stirling-pdf.png
Normal file
BIN
docs/stirling-pdf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
1
docs/stirling-transparent.svg
Normal file
1
docs/stirling-transparent.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.4 KiB |
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security-Fat-with-login
|
container_name: Stirling-PDF-Security-Fat
|
||||||
image: stirlingtools/stirling-pdf:latest-fat
|
image: stirlingtools/stirling-pdf:latest-fat
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 145 KiB |
@@ -75,7 +75,7 @@ def write_readme(progress_list: list[tuple[str, int]]) -> None:
|
|||||||
f"",
|
f"",
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
file.writelines(content)
|
||||||
|
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ def compare_files(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
ignore_translation = convert_to_multiline(sort_ignore_translation)
|
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))
|
file.write(tomlkit.dumps(ignore_translation))
|
||||||
|
|
||||||
unique_data = list(set(result_list))
|
unique_data = list(set(result_list))
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ ignore = [
|
|||||||
[cs_CZ]
|
[cs_CZ]
|
||||||
ignore = [
|
ignore = [
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
'pipeline.header',
|
||||||
'text',
|
'text',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -49,7 +50,6 @@ ignore = [
|
|||||||
'pipeline.title',
|
'pipeline.title',
|
||||||
'pipelineOptions.pipelineHeader',
|
'pipelineOptions.pipelineHeader',
|
||||||
'pro',
|
'pro',
|
||||||
'redact.zoom',
|
|
||||||
'sponsor',
|
'sponsor',
|
||||||
'text',
|
'text',
|
||||||
'validateSignature.cert.bits',
|
'validateSignature.cert.bits',
|
||||||
@@ -210,11 +210,6 @@ ignore = [
|
|||||||
'watermark.type.1',
|
'watermark.type.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
[sl_SI]
|
|
||||||
ignore = [
|
|
||||||
'language.direction',
|
|
||||||
]
|
|
||||||
|
|
||||||
[sr_LATN_RS]
|
[sr_LATN_RS]
|
||||||
ignore = [
|
ignore = [
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import org.springframework.core.Ordered;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -26,7 +25,7 @@ public class EEAppConfig {
|
|||||||
|
|
||||||
@Bean(name = "runningEE")
|
@Bean(name = "runningEE")
|
||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "SSOAutoLogin")
|
@Bean(name = "SSOAutoLogin")
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.posthog.java.shaded.org.json.JSONObject;
|
import com.posthog.java.shaded.org.json.JSONObject;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class KeygenLicenseVerifier {
|
public class KeygenLicenseVerifier {
|
||||||
// todo: place in config files?
|
|
||||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||||
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
@@ -69,7 +67,7 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error verifying license: {}", e.getMessage());
|
log.error("Error verifying license: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,9 +94,10 @@ public class KeygenLicenseVerifier {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.debug("ValidateLicenseResponse body: {}", response.body());
|
log.debug(" validateLicenseResponse body: " + response.body());
|
||||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
JsonNode metaNode = jsonResponse.path("meta");
|
JsonNode metaNode = jsonResponse.path("meta");
|
||||||
boolean isValid = metaNode.path("valid").asBoolean();
|
boolean isValid = metaNode.path("valid").asBoolean();
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@ public class KeygenLicenseVerifier {
|
|||||||
log.info(applicationProperties.toString());
|
log.info(applicationProperties.toString());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.error("Error validating license. Status code: {}", response.statusCode());
|
log.error("Error validating license. Status code: " + response.statusCode());
|
||||||
}
|
}
|
||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.springframework.scheduling.annotation.Scheduled;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ public class LicenseKeyChecker {
|
|||||||
|
|
||||||
public void updateLicenseKey(String newKey) throws IOException {
|
public void updateLicenseKey(String newKey) throws IOException {
|
||||||
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||||
GeneralUtils.saveKeyToSettings("EnterpriseEdition.key", newKey);
|
GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false);
|
||||||
checkLicense();
|
checkLicense();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -21,14 +22,11 @@ import io.github.pixee.security.SystemCommand;
|
|||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.UI.WebBrowser;
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@@ -64,12 +62,6 @@ public class SPDFApplication {
|
|||||||
app.setHeadless(false);
|
app.setHeadless(false);
|
||||||
props.put("java.awt.headless", "false");
|
props.put("java.awt.headless", "false");
|
||||||
props.put("spring.main.web-application-type", "servlet");
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
|
|
||||||
int desiredPort = 8080;
|
|
||||||
String port = UrlUtils.findAvailablePort(desiredPort);
|
|
||||||
props.put("server.port", port);
|
|
||||||
System.setProperty("server.port", port);
|
|
||||||
log.info("Desktop UI mode: Using port {}", port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setAdditionalProfiles(getActiveProfile(args));
|
app.setAdditionalProfiles(getActiveProfile(args));
|
||||||
@@ -83,18 +75,18 @@ public class SPDFApplication {
|
|||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
// External config files
|
// External config files
|
||||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath());
|
||||||
log.info("Settings file: {}", settingsPath.toString());
|
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) {
|
||||||
if (Files.exists(settingsPath)) {
|
|
||||||
propertyFiles.put(
|
propertyFiles.put(
|
||||||
"spring.config.additional-location", "file:" + settingsPath.toString());
|
"spring.config.additional-location",
|
||||||
|
"file:" + InstallationPathConfig.getSettingsPath());
|
||||||
} else {
|
} else {
|
||||||
log.warn("External configuration file '{}' does not exist.", settingsPath.toString());
|
log.warn(
|
||||||
|
"External configuration file '{}' does not exist.",
|
||||||
|
InstallationPathConfig.getSettingsPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) {
|
||||||
log.info("Custom settings file: {}", customSettingsPath.toString());
|
|
||||||
if (Files.exists(customSettingsPath)) {
|
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
if (!existingLocation.isEmpty()) {
|
if (!existingLocation.isEmpty()) {
|
||||||
@@ -102,11 +94,11 @@ public class SPDFApplication {
|
|||||||
}
|
}
|
||||||
propertyFiles.put(
|
propertyFiles.put(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
existingLocation + "file:" + customSettingsPath.toString());
|
existingLocation + "file:" + InstallationPathConfig.getCustomSettingsPath());
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Custom configuration file '{}' does not exist.",
|
"Custom configuration file '{}' does not exist.",
|
||||||
customSettingsPath.toString());
|
InstallationPathConfig.getCustomSettingsPath());
|
||||||
}
|
}
|
||||||
Properties finalProps = new Properties();
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
@@ -128,7 +120,7 @@ public class SPDFApplication {
|
|||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
|
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
|
||||||
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
|
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
log.error("Error creating directories: {}", e.getMessage());
|
log.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +149,7 @@ public class SPDFApplication {
|
|||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
log.error("Error opening browser: {}", e.getMessage());
|
log.error("Error opening browser: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,17 +158,7 @@ public class SPDFApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPort(String port) {
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setServerPortStatic(String port) {
|
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
if ("auto".equalsIgnoreCase(port)) {
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
SPDFApplication.serverPortStatic =
|
SPDFApplication.serverPortStatic =
|
||||||
@@ -213,11 +195,36 @@ public class SPDFApplication {
|
|||||||
return new String[] {"default"};
|
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() {
|
public static String getStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNonStaticBaseUrl() {
|
||||||
|
return baseUrlStatic;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticPort() {
|
public static String getStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNonStaticPort() {
|
||||||
|
return serverPortStatic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import me.friwi.jcefmaven.CefAppBuilder;
|
import me.friwi.jcefmaven.CefAppBuilder;
|
||||||
import me.friwi.jcefmaven.EnumProgress;
|
import me.friwi.jcefmaven.EnumProgress;
|
||||||
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||||
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||||
|
|
||||||
import stirling.software.SPDF.UI.WebBrowser;
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.utils.UIScaling;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -219,7 +215,7 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));
|
frame.setSize(1280, 768);
|
||||||
frame.setLocationRelativeTo(null);
|
frame.setLocationRelativeTo(null);
|
||||||
|
|
||||||
loadIcon();
|
loadIcon();
|
||||||
@@ -268,9 +264,7 @@ public class DesktopBrowser implements WebBrowser {
|
|||||||
frame.setOpacity(1.0f);
|
frame.setOpacity(1.0f);
|
||||||
frame.setUndecorated(false);
|
frame.setUndecorated(false);
|
||||||
frame.pack();
|
frame.pack();
|
||||||
frame.setSize(
|
frame.setSize(1280, 800);
|
||||||
UIScaling.scaleWidth(1280),
|
|
||||||
UIScaling.scaleHeight(800));
|
|
||||||
frame.setLocationRelativeTo(null);
|
frame.setLocationRelativeTo(null);
|
||||||
log.debug("Frame reconfigured");
|
log.debug("Frame reconfigured");
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
package stirling.software.SPDF.UI.impl;
|
package stirling.software.SPDF.UI.impl;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import io.github.pixee.security.BoundedLineReader;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.UIScaling;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoadingWindow extends JDialog {
|
public class LoadingWindow extends JDialog {
|
||||||
private final JProgressBar progressBar;
|
private final JProgressBar progressBar;
|
||||||
@@ -25,13 +16,6 @@ public class LoadingWindow extends JDialog {
|
|||||||
private final JLabel brandLabel;
|
private final JLabel brandLabel;
|
||||||
private long startTime;
|
private long startTime;
|
||||||
|
|
||||||
private Timer stuckTimer;
|
|
||||||
private long stuckThreshold = 4000;
|
|
||||||
private long timeAt90Percent = -1;
|
|
||||||
private volatile Process explorerProcess;
|
|
||||||
private static final boolean IS_WINDOWS =
|
|
||||||
System.getProperty("os.name").toLowerCase().contains("win");
|
|
||||||
|
|
||||||
public LoadingWindow(Frame parent, String initialUrl) {
|
public LoadingWindow(Frame parent, String initialUrl) {
|
||||||
super(parent, "Initializing Stirling-PDF", true);
|
super(parent, "Initializing Stirling-PDF", true);
|
||||||
startTime = System.currentTimeMillis();
|
startTime = System.currentTimeMillis();
|
||||||
@@ -57,12 +41,12 @@ public class LoadingWindow extends JDialog {
|
|||||||
if (is != null) {
|
if (is != null) {
|
||||||
Image img = ImageIO.read(is);
|
Image img = ImageIO.read(is);
|
||||||
if (img != null) {
|
if (img != null) {
|
||||||
Image scaledImg = UIScaling.scaleIcon(img, 48, 48);
|
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
|
||||||
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
||||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
gbc.gridy = 0;
|
gbc.gridy = 0;
|
||||||
mainPanel.add(iconLabel, gbc);
|
mainPanel.add(iconLabel, gbc);
|
||||||
log.info("Icon loaded and scaled successfully");
|
log.debug("Icon loaded and scaled successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,8 +83,7 @@ public class LoadingWindow extends JDialog {
|
|||||||
setUndecorated(false);
|
setUndecorated(false);
|
||||||
|
|
||||||
// Set size and position
|
// Set size and position
|
||||||
setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200));
|
setSize(400, 200);
|
||||||
|
|
||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
setAlwaysOnTop(true);
|
setAlwaysOnTop(true);
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
@@ -111,163 +94,6 @@ public class LoadingWindow extends JDialog {
|
|||||||
System.currentTimeMillis() - startTime);
|
System.currentTimeMillis() - startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndRefreshExplorer() {
|
|
||||||
if (!IS_WINDOWS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (timeAt90Percent == -1) {
|
|
||||||
timeAt90Percent = System.currentTimeMillis();
|
|
||||||
stuckTimer =
|
|
||||||
new Timer(
|
|
||||||
1000,
|
|
||||||
e -> {
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
if (currentTime - timeAt90Percent > stuckThreshold) {
|
|
||||||
try {
|
|
||||||
log.debug(
|
|
||||||
"Attempting Windows explorer refresh due to 90% stuck state");
|
|
||||||
String currentDir = System.getProperty("user.dir");
|
|
||||||
|
|
||||||
// Store current explorer PIDs before we start new one
|
|
||||||
Set<String> existingPids = new HashSet<>();
|
|
||||||
ProcessBuilder listExplorer =
|
|
||||||
new ProcessBuilder(
|
|
||||||
"cmd",
|
|
||||||
"/c",
|
|
||||||
"wmic",
|
|
||||||
"process",
|
|
||||||
"where",
|
|
||||||
"name='explorer.exe'",
|
|
||||||
"get",
|
|
||||||
"ProcessId",
|
|
||||||
"/format:csv");
|
|
||||||
Process process = listExplorer.start();
|
|
||||||
BufferedReader reader =
|
|
||||||
new BufferedReader(
|
|
||||||
new InputStreamReader(
|
|
||||||
process.getInputStream()));
|
|
||||||
String line;
|
|
||||||
while ((line =
|
|
||||||
BoundedLineReader.readLine(
|
|
||||||
reader, 5_000_000))
|
|
||||||
!= null) {
|
|
||||||
if (line.matches(".*\\d+.*")) { // Contains numbers
|
|
||||||
String[] parts = line.trim().split(",");
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
existingPids.add(
|
|
||||||
parts[parts.length - 1].trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process.waitFor(2, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
// Start new explorer
|
|
||||||
ProcessBuilder pb =
|
|
||||||
new ProcessBuilder(
|
|
||||||
"cmd",
|
|
||||||
"/c",
|
|
||||||
"start",
|
|
||||||
"/min",
|
|
||||||
"/b",
|
|
||||||
"explorer.exe",
|
|
||||||
currentDir);
|
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
explorerProcess = pb.start();
|
|
||||||
|
|
||||||
// Schedule cleanup
|
|
||||||
Timer cleanupTimer =
|
|
||||||
new Timer(
|
|
||||||
2000,
|
|
||||||
cleanup -> {
|
|
||||||
try {
|
|
||||||
// Find new explorer processes
|
|
||||||
ProcessBuilder findNewExplorer =
|
|
||||||
new ProcessBuilder(
|
|
||||||
"cmd",
|
|
||||||
"/c",
|
|
||||||
"wmic",
|
|
||||||
"process",
|
|
||||||
"where",
|
|
||||||
"name='explorer.exe'",
|
|
||||||
"get",
|
|
||||||
"ProcessId",
|
|
||||||
"/format:csv");
|
|
||||||
Process newProcess =
|
|
||||||
findNewExplorer.start();
|
|
||||||
BufferedReader newReader =
|
|
||||||
new BufferedReader(
|
|
||||||
new InputStreamReader(
|
|
||||||
newProcess
|
|
||||||
.getInputStream()));
|
|
||||||
String newLine;
|
|
||||||
while ((newLine =
|
|
||||||
BoundedLineReader
|
|
||||||
.readLine(
|
|
||||||
newReader,
|
|
||||||
5_000_000))
|
|
||||||
!= null) {
|
|
||||||
if (newLine.matches(
|
|
||||||
".*\\d+.*")) {
|
|
||||||
String[] parts =
|
|
||||||
newLine.trim()
|
|
||||||
.split(",");
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
String pid =
|
|
||||||
parts[
|
|
||||||
parts.length
|
|
||||||
- 1]
|
|
||||||
.trim();
|
|
||||||
if (!existingPids
|
|
||||||
.contains(
|
|
||||||
pid)) {
|
|
||||||
log.debug(
|
|
||||||
"Found new explorer.exe with PID: "
|
|
||||||
+ pid);
|
|
||||||
ProcessBuilder
|
|
||||||
killProcess =
|
|
||||||
new ProcessBuilder(
|
|
||||||
"taskkill",
|
|
||||||
"/PID",
|
|
||||||
pid,
|
|
||||||
"/F");
|
|
||||||
killProcess
|
|
||||||
.redirectErrorStream(
|
|
||||||
true);
|
|
||||||
Process killResult =
|
|
||||||
killProcess
|
|
||||||
.start();
|
|
||||||
killResult.waitFor(
|
|
||||||
2,
|
|
||||||
TimeUnit
|
|
||||||
.SECONDS);
|
|
||||||
log.debug(
|
|
||||||
"Explorer process terminated: "
|
|
||||||
+ pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newProcess.waitFor(
|
|
||||||
2, TimeUnit.SECONDS);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error(
|
|
||||||
"Error cleaning up Windows explorer process",
|
|
||||||
ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cleanupTimer.setRepeats(false);
|
|
||||||
cleanupTimer.start();
|
|
||||||
stuckTimer.stop();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error("Error refreshing Windows explorer", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
stuckTimer.setRepeats(true);
|
|
||||||
stuckTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(final int progress) {
|
public void setProgress(final int progress) {
|
||||||
SwingUtilities.invokeLater(
|
SwingUtilities.invokeLater(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -289,23 +115,11 @@ public class LoadingWindow extends JDialog {
|
|||||||
|
|
||||||
// Add thread state logging
|
// Add thread state logging
|
||||||
Thread currentThread = Thread.currentThread();
|
Thread currentThread = Thread.currentThread();
|
||||||
log.info(
|
log.debug(
|
||||||
"Current thread state - Name: {}, State: {}, Priority: {}",
|
"Current thread state - Name: {}, State: {}, Priority: {}",
|
||||||
currentThread.getName(),
|
currentThread.getName(),
|
||||||
currentThread.getState(),
|
currentThread.getState(),
|
||||||
currentThread.getPriority());
|
currentThread.getPriority());
|
||||||
|
|
||||||
if (validProgress >= 90 && validProgress < 95) {
|
|
||||||
checkAndRefreshExplorer();
|
|
||||||
} else {
|
|
||||||
// Reset the timer if we move past 95%
|
|
||||||
if (validProgress >= 95) {
|
|
||||||
if (stuckTimer != null) {
|
|
||||||
stuckTimer.stop();
|
|
||||||
}
|
|
||||||
timeAt90Percent = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setValue(validProgress);
|
progressBar.setValue(validProgress);
|
||||||
@@ -331,7 +145,7 @@ public class LoadingWindow extends JDialog {
|
|||||||
statusLabel.setText(validStatus);
|
statusLabel.setText(validStatus);
|
||||||
|
|
||||||
// Log UI state when status changes
|
// Log UI state when status changes
|
||||||
log.info(
|
log.debug(
|
||||||
"UI State - Window visible: {}, Progress: {}%, Status: {}",
|
"UI State - Window visible: {}, Progress: {}%, Status: {}",
|
||||||
isVisible(), progressBar.getValue(), validStatus);
|
isVisible(), progressBar.getValue(), validStatus);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ import org.springframework.core.io.ResourceLoader;
|
|||||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -35,7 +33,10 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
name = "system.customHTMLFiles",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
||||||
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
||||||
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
||||||
@@ -73,11 +74,6 @@ public class AppConfig {
|
|||||||
: "null";
|
: "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "languages")
|
|
||||||
public List<String> languages() {
|
|
||||||
return applicationProperties.getUi().getLanguages();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "navBarText")
|
@Bean(name = "navBarText")
|
||||||
public String navBarText() {
|
public String navBarText() {
|
||||||
String defaultNavBar =
|
String defaultNavBar =
|
||||||
@@ -96,9 +92,9 @@ public class AppConfig {
|
|||||||
|
|
||||||
@Bean(name = "rateLimit")
|
@Bean(name = "rateLimit")
|
||||||
public boolean rateLimit() {
|
public boolean rateLimit() {
|
||||||
String rateLimit = System.getProperty("rateLimit");
|
String appName = System.getProperty("rateLimit");
|
||||||
if (rateLimit == null) rateLimit = System.getenv("rateLimit");
|
if (appName == null) appName = System.getenv("rateLimit");
|
||||||
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
|
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "RunningInDocker")
|
@Bean(name = "RunningInDocker")
|
||||||
@@ -125,9 +121,18 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "bookAndHtmlFormatsInstalled")
|
||||||
|
public boolean bookAndHtmlFormatsInstalled() {
|
||||||
|
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
|
||||||
|
if (installOps == null) {
|
||||||
|
installOps = System.getenv("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
|
||||||
|
}
|
||||||
|
return "true".equalsIgnoreCase(installOps);
|
||||||
|
}
|
||||||
|
|
||||||
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
||||||
@Bean(name = "activeSecurity")
|
@Bean(name = "activSecurity")
|
||||||
public boolean missingActiveSecurity() {
|
public boolean missingActivSecurity() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,14 +175,16 @@ public class AppConfig {
|
|||||||
@Bean(name = "analyticsPrompt")
|
@Bean(name = "analyticsPrompt")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public boolean analyticsPrompt() {
|
public boolean analyticsPrompt() {
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() == null;
|
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||||
|
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsEnabled")
|
@Bean(name = "analyticsEnabled")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public boolean analyticsEnabled() {
|
public boolean analyticsEnabled() {
|
||||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||||
return applicationProperties.getSystem().isAnalyticsEnabled();
|
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||||
|
&& Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "StirlingPDFLabel")
|
@Bean(name = "StirlingPDFLabel")
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package stirling.software.SPDF.config;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Service
|
||||||
class AppUpdateService {
|
class AppUpdateService {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
|||||||
"endpoints",
|
"endpoints",
|
||||||
"logout",
|
"logout",
|
||||||
"error",
|
"error",
|
||||||
"errorOAuth",
|
"erroroauth",
|
||||||
"file",
|
"file",
|
||||||
"messageType",
|
"messageType",
|
||||||
"infoMessage");
|
"infoMessage");
|
||||||
|
|||||||
@@ -9,71 +9,135 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.simpleyaml.configuration.comments.CommentType;
|
||||||
|
import org.simpleyaml.configuration.file.YamlFile;
|
||||||
|
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||||
|
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
|
||||||
* A naive, line-based approach to merging "settings.yml" with "settings.yml.template" while
|
|
||||||
* preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
|
|
||||||
* the merged content actually differs.
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ConfigInitializer {
|
public class ConfigInitializer {
|
||||||
|
|
||||||
public void ensureConfigExists() throws IOException, URISyntaxException {
|
public void ensureConfigExists() throws IOException, URISyntaxException {
|
||||||
// 1) If settings file doesn't exist, create from template
|
// Define the path to the external config directory
|
||||||
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
|
|
||||||
|
// Check if the file already exists
|
||||||
if (Files.notExists(destPath)) {
|
if (Files.notExists(destPath)) {
|
||||||
|
// Ensure the destination directory exists
|
||||||
Files.createDirectories(destPath.getParent());
|
Files.createDirectories(destPath.getParent());
|
||||||
|
|
||||||
|
// Copy the resource from classpath to the external directory
|
||||||
try (InputStream in =
|
try (InputStream in =
|
||||||
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||||
if (in == null) {
|
if (in != null) {
|
||||||
|
Files.copy(in, destPath);
|
||||||
|
} else {
|
||||||
throw new FileNotFoundException(
|
throw new FileNotFoundException(
|
||||||
"Resource file not found: settings.yml.template");
|
"Resource file not found: settings.yml.template");
|
||||||
}
|
}
|
||||||
Files.copy(in, destPath);
|
|
||||||
}
|
}
|
||||||
log.info("Created settings file from template");
|
log.info("Created settings file from template");
|
||||||
} else {
|
} else {
|
||||||
// 2) Merge existing file with the template
|
|
||||||
URL templateResource = getClass().getClassLoader().getResource("settings.yml.template");
|
// Define the path to the config settings file
|
||||||
if (templateResource == null) {
|
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
|
// Load the template resource
|
||||||
|
URL settingsTemplateResource =
|
||||||
|
getClass().getClassLoader().getResource("settings.yml.template");
|
||||||
|
if (settingsTemplateResource == null) {
|
||||||
throw new IOException("Resource not found: settings.yml.template");
|
throw new IOException("Resource not found: settings.yml.template");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy template to a temp location so we can read lines
|
// Create a temporary file to copy the resource content
|
||||||
Path tempTemplatePath = Files.createTempFile("settings.yml", ".template");
|
Path tempTemplatePath = Files.createTempFile("settings.yml", ".template");
|
||||||
try (InputStream in = templateResource.openStream()) {
|
|
||||||
|
try (InputStream in = settingsTemplateResource.openStream()) {
|
||||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy setting.yaml to a temp location so we can read lines
|
final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
|
||||||
Path settingTempPath = Files.createTempFile("settings", ".yaml");
|
DumperOptions yamlOptionsSettingsTemplateFile =
|
||||||
try (InputStream in = Files.newInputStream(destPath)) {
|
((SimpleYamlImplementation) settingsTemplateFile.getImplementation())
|
||||||
Files.copy(in, settingTempPath, StandardCopyOption.REPLACE_EXISTING);
|
.getDumperOptions();
|
||||||
|
yamlOptionsSettingsTemplateFile.setSplitLines(false);
|
||||||
|
settingsTemplateFile.loadWithComments();
|
||||||
|
|
||||||
|
final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
|
||||||
|
DumperOptions yamlOptionsSettingsFile =
|
||||||
|
((SimpleYamlImplementation) settingsFile.getImplementation())
|
||||||
|
.getDumperOptions();
|
||||||
|
yamlOptionsSettingsFile.setSplitLines(false);
|
||||||
|
settingsFile.loadWithComments();
|
||||||
|
|
||||||
|
// Load headers and comments
|
||||||
|
String header = settingsTemplateFile.getHeader();
|
||||||
|
|
||||||
|
// Create a new file for temporary settings
|
||||||
|
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
|
||||||
|
DumperOptions yamlOptionsTempSettingFile =
|
||||||
|
((SimpleYamlImplementation) tempSettingFile.getImplementation())
|
||||||
|
.getDumperOptions();
|
||||||
|
yamlOptionsTempSettingFile.setSplitLines(false);
|
||||||
|
tempSettingFile.createNewFile(true);
|
||||||
|
tempSettingFile.setHeader(header);
|
||||||
|
|
||||||
|
// Get all keys from the template
|
||||||
|
List<String> keys =
|
||||||
|
Arrays.asList(settingsTemplateFile.getKeys(true).toArray(new String[0]));
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
if (!key.contains(".")) {
|
||||||
|
// Add blank lines and comments to specific sections
|
||||||
|
tempSettingFile
|
||||||
|
.path(key)
|
||||||
|
.comment(settingsTemplateFile.getComment(key))
|
||||||
|
.blankLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Copy settings from the template to the settings.yml file
|
||||||
|
changeConfigItemFromCommentToKeyValue(
|
||||||
|
settingsTemplateFile, settingsFile, tempSettingFile, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
YamlHelper settingsTemplateFile = new YamlHelper(tempTemplatePath);
|
// Save the settings.yml file
|
||||||
YamlHelper settingsFile = new YamlHelper(settingTempPath);
|
tempSettingFile.save();
|
||||||
|
|
||||||
boolean changesMade =
|
|
||||||
settingsTemplateFile.updateValuesFromYaml(settingsFile, settingsTemplateFile);
|
|
||||||
if (changesMade) {
|
|
||||||
settingsTemplateFile.save(destPath);
|
|
||||||
log.info("Settings file updated based on template changes.");
|
|
||||||
} else {
|
|
||||||
log.info("No changes detected; settings file left as-is.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Files.deleteIfExists(tempTemplatePath);
|
|
||||||
Files.deleteIfExists(settingTempPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Ensure custom settings file exists
|
// Create custom settings file if it doesn't exist
|
||||||
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||||
if (Files.notExists(customSettingsPath)) {
|
if (!Files.exists(customSettingsPath)) {
|
||||||
Files.createFile(customSettingsPath);
|
Files.createFile(customSettingsPath);
|
||||||
log.info("Created custom_settings file: {}", customSettingsPath.toString());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeConfigItemFromCommentToKeyValue(
|
||||||
|
final YamlFile settingsTemplateFile,
|
||||||
|
final YamlFile settingsFile,
|
||||||
|
final YamlFile tempSettingFile,
|
||||||
|
String path) {
|
||||||
|
if (settingsFile.get(path) == null && settingsTemplateFile.get(path) != null) {
|
||||||
|
// If the key is only in the template, add it to the temporary settings with comments
|
||||||
|
tempSettingFile
|
||||||
|
.path(path)
|
||||||
|
.set(settingsTemplateFile.get(path))
|
||||||
|
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||||
|
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||||
|
} else if (settingsFile.get(path) != null && settingsTemplateFile.get(path) != null) {
|
||||||
|
// If the key is in both, update the temporary settings with the main settings' value
|
||||||
|
// and comments
|
||||||
|
tempSettingFile
|
||||||
|
.path(path)
|
||||||
|
.set(settingsFile.get(path))
|
||||||
|
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||||
|
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||||
|
} else {
|
||||||
|
// Log if the key is not found in both YAML files
|
||||||
|
log.info("Key not found in both YAML files: " + path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -8,24 +9,30 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@DependsOn({"bookAndHtmlFormatsInstalled"})
|
||||||
public class EndpointConfiguration {
|
public class EndpointConfiguration {
|
||||||
|
|
||||||
private static final String REMOVE_BLANKS = "remove-blanks";
|
private static final String REMOVE_BLANKS = "remove-blanks";
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||||
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public EndpointConfiguration(ApplicationProperties applicationProperties) {
|
public EndpointConfiguration(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
init();
|
init();
|
||||||
processEnvironmentConfigs();
|
processEnvironmentConfigs();
|
||||||
}
|
}
|
||||||
@@ -119,7 +126,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Convert", "url-to-pdf");
|
addEndpointToGroup("Convert", "url-to-pdf");
|
||||||
addEndpointToGroup("Convert", "markdown-to-pdf");
|
addEndpointToGroup("Convert", "markdown-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||||
addEndpointToGroup("Convert", "pdf-to-markdown");
|
|
||||||
|
|
||||||
// Adding endpoints to "Security" group
|
// Adding endpoints to "Security" group
|
||||||
addEndpointToGroup("Security", "add-password");
|
addEndpointToGroup("Security", "add-password");
|
||||||
@@ -190,8 +196,8 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||||
|
|
||||||
// Unoconvert
|
// Unoconv
|
||||||
addEndpointToGroup("Unoconvert", "file-to-pdf");
|
addEndpointToGroup("Unoconv", "file-to-pdf");
|
||||||
|
|
||||||
// qpdf
|
// qpdf
|
||||||
addEndpointToGroup("qpdf", "compress-pdf");
|
addEndpointToGroup("qpdf", "compress-pdf");
|
||||||
@@ -237,7 +243,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||||
addEndpointToGroup("Java", "pdf-to-text");
|
addEndpointToGroup("Java", "pdf-to-text");
|
||||||
addEndpointToGroup("Java", "remove-image-pdf");
|
addEndpointToGroup("Java", "remove-image-pdf");
|
||||||
addEndpointToGroup("Java", "pdf-to-markdown");
|
|
||||||
|
|
||||||
// Javascript
|
// Javascript
|
||||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||||
@@ -253,11 +258,12 @@ public class EndpointConfiguration {
|
|||||||
// Weasyprint dependent endpoints
|
// Weasyprint dependent endpoints
|
||||||
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
||||||
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
||||||
addEndpointToGroup("Weasyprint", "markdown-to-pdf");
|
|
||||||
|
|
||||||
// Pdftohtml dependent endpoints
|
// Pdftohtml dependent endpoints
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
|
|
||||||
|
// disabled for now while we resolve issues
|
||||||
|
disableEndpoint("pdf-to-pdfa");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
@@ -265,6 +271,12 @@ public class EndpointConfiguration {
|
|||||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||||
|
|
||||||
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
|
if (groupsToRemove == null) {
|
||||||
|
groupsToRemove = new ArrayList<>();
|
||||||
|
}
|
||||||
|
groupsToRemove.add("Calibre");
|
||||||
|
}
|
||||||
if (endpointsToRemove != null) {
|
if (endpointsToRemove != null) {
|
||||||
for (String endpoint : endpointsToRemove) {
|
for (String endpoint : endpointsToRemove) {
|
||||||
disableEndpoint(endpoint.trim());
|
disableEndpoint(endpoint.trim());
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.util.stream.Collectors;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -17,29 +16,21 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
public class ExternalAppDepConfig {
|
public class ExternalAppDepConfig {
|
||||||
|
|
||||||
private final EndpointConfiguration endpointConfiguration;
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
private final Map<String, List<String>> commandToGroupMapping =
|
||||||
|
new HashMap<>() {
|
||||||
|
|
||||||
private final String weasyprintPath;
|
{
|
||||||
private final String unoconvPath;
|
put("soffice", List.of("LibreOffice"));
|
||||||
private final Map<String, List<String>> commandToGroupMapping;
|
put("weasyprint", List.of("Weasyprint"));
|
||||||
|
put("pdftohtml", List.of("Pdftohtml"));
|
||||||
|
put("unoconv", List.of("Unoconv"));
|
||||||
|
put("qpdf", List.of("qpdf"));
|
||||||
|
put("tesseract", List.of("tesseract"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public ExternalAppDepConfig(
|
public ExternalAppDepConfig(EndpointConfiguration endpointConfiguration) {
|
||||||
EndpointConfiguration endpointConfiguration, RuntimePathConfig runtimePathConfig) {
|
|
||||||
this.endpointConfiguration = endpointConfiguration;
|
this.endpointConfiguration = endpointConfiguration;
|
||||||
weasyprintPath = runtimePathConfig.getWeasyPrintPath();
|
|
||||||
unoconvPath = runtimePathConfig.getUnoConvertPath();
|
|
||||||
|
|
||||||
commandToGroupMapping =
|
|
||||||
new HashMap<>() {
|
|
||||||
|
|
||||||
{
|
|
||||||
put("soffice", List.of("LibreOffice"));
|
|
||||||
put(weasyprintPath, List.of("Weasyprint"));
|
|
||||||
put("pdftohtml", List.of("Pdftohtml"));
|
|
||||||
put(unoconvPath, List.of("Unoconvert"));
|
|
||||||
put("qpdf", List.of("qpdf"));
|
|
||||||
put("tesseract", List.of("tesseract"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCommandAvailable(String command) {
|
private boolean isCommandAvailable(String command) {
|
||||||
@@ -110,9 +101,9 @@ public class ExternalAppDepConfig {
|
|||||||
checkDependencyAndDisableGroup("tesseract");
|
checkDependencyAndDisableGroup("tesseract");
|
||||||
checkDependencyAndDisableGroup("soffice");
|
checkDependencyAndDisableGroup("soffice");
|
||||||
checkDependencyAndDisableGroup("qpdf");
|
checkDependencyAndDisableGroup("qpdf");
|
||||||
checkDependencyAndDisableGroup(weasyprintPath);
|
checkDependencyAndDisableGroup("weasyprint");
|
||||||
checkDependencyAndDisableGroup("pdftohtml");
|
checkDependencyAndDisableGroup("pdftohtml");
|
||||||
checkDependencyAndDisableGroup(unoconvPath);
|
checkDependencyAndDisableGroup("unoconv");
|
||||||
// Special handling for Python/OpenCV dependencies
|
// Special handling for Python/OpenCV dependencies
|
||||||
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
|
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
|
||||||
if (!pythonAvailable) {
|
if (!pythonAvailable) {
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@@ -44,7 +42,7 @@ public class InitialSetup {
|
|||||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
// Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
uuid = UUID.randomUUID().toString();
|
uuid = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.UUID", uuid);
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
||||||
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +52,7 @@ public class InitialSetup {
|
|||||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
// Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
secretKey = UUID.randomUUID().toString();
|
secretKey = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.key", secretKey);
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
||||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +62,8 @@ public class InitialSetup {
|
|||||||
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||||
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||||
if (!csrf) {
|
if (!csrf) {
|
||||||
GeneralUtils.saveKeyToSettings("security.csrfDisabled", false);
|
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
|
||||||
GeneralUtils.saveKeyToSettings("system.enableAnalytics", true);
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
|
||||||
applicationProperties.getSecurity().setCsrfDisabled(false);
|
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,14 +74,14 @@ public class InitialSetup {
|
|||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
if (StringUtils.isEmpty(termsUrl)) {
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
||||||
GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl);
|
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
||||||
}
|
}
|
||||||
// Initialize Privacy Policy
|
// Initialize Privacy Policy
|
||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
if (StringUtils.isEmpty(privacyUrl)) {
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
||||||
GeneralUtils.saveKeyToSettings("legal.privacyPolicy", defaultPrivacyUrl);
|
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +95,7 @@ public class InitialSetup {
|
|||||||
appVersion = props.getProperty("version");
|
appVersion = props.getProperty("version");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.appVersion", appVersion);
|
|
||||||
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||||
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -12,6 +11,7 @@ public class InstallationPathConfig {
|
|||||||
// Root paths
|
// Root paths
|
||||||
private static final String LOG_PATH;
|
private static final String LOG_PATH;
|
||||||
private static final String CONFIG_PATH;
|
private static final String CONFIG_PATH;
|
||||||
|
private static final String PIPELINE_PATH;
|
||||||
private static final String CUSTOM_FILES_PATH;
|
private static final String CUSTOM_FILES_PATH;
|
||||||
private static final String CLIENT_WEBUI_PATH;
|
private static final String CLIENT_WEBUI_PATH;
|
||||||
|
|
||||||
@@ -19,6 +19,11 @@ public class InstallationPathConfig {
|
|||||||
private static final String SETTINGS_PATH;
|
private static final String SETTINGS_PATH;
|
||||||
private static final String CUSTOM_SETTINGS_PATH;
|
private static final String CUSTOM_SETTINGS_PATH;
|
||||||
|
|
||||||
|
// Pipeline paths
|
||||||
|
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
|
||||||
|
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
|
private static final String PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
|
||||||
// Custom file paths
|
// Custom file paths
|
||||||
private static final String STATIC_PATH;
|
private static final String STATIC_PATH;
|
||||||
private static final String TEMPLATES_PATH;
|
private static final String TEMPLATES_PATH;
|
||||||
@@ -30,6 +35,7 @@ public class InstallationPathConfig {
|
|||||||
// Initialize root paths
|
// Initialize root paths
|
||||||
LOG_PATH = BASE_PATH + "logs" + File.separator;
|
LOG_PATH = BASE_PATH + "logs" + File.separator;
|
||||||
CONFIG_PATH = BASE_PATH + "configs" + File.separator;
|
CONFIG_PATH = BASE_PATH + "configs" + File.separator;
|
||||||
|
PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator;
|
||||||
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
|
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
|
||||||
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
|
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
|
||||||
|
|
||||||
@@ -37,6 +43,11 @@ public class InstallationPathConfig {
|
|||||||
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
||||||
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
||||||
|
|
||||||
|
// Initialize pipeline paths
|
||||||
|
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator;
|
||||||
|
PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator;
|
||||||
|
PIPELINE_DEFAULT_WEB_UI_CONFIGS = PIPELINE_PATH + "defaultWebUIConfigs" + File.separator;
|
||||||
|
|
||||||
// Initialize custom file paths
|
// Initialize custom file paths
|
||||||
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||||
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
|
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
|
||||||
@@ -47,29 +58,26 @@ public class InstallationPathConfig {
|
|||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
return Paths.get(
|
return System.getenv("APPDATA") + File.separator + "Stirling-PDF" + File.separator;
|
||||||
System.getenv("APPDATA"), // parent path
|
|
||||||
"Stirling-PDF")
|
|
||||||
.toString()
|
|
||||||
+ File.separator;
|
|
||||||
} else if (os.contains("mac")) {
|
} else if (os.contains("mac")) {
|
||||||
return Paths.get(
|
return System.getProperty("user.home")
|
||||||
System.getProperty("user.home"),
|
+ File.separator
|
||||||
"Library",
|
+ "Library"
|
||||||
"Application Support",
|
+ File.separator
|
||||||
"Stirling-PDF")
|
+ "Application Support"
|
||||||
.toString()
|
+ File.separator
|
||||||
|
+ "Stirling-PDF"
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
} else {
|
} else {
|
||||||
return Paths.get(
|
return System.getProperty("user.home")
|
||||||
System.getProperty("user.home"), // parent path
|
+ File.separator
|
||||||
".config",
|
+ ".config"
|
||||||
"Stirling-PDF")
|
+ File.separator
|
||||||
.toString()
|
+ "Stirling-PDF"
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "." + File.separator;
|
return "./";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPath() {
|
public static String getPath() {
|
||||||
@@ -84,6 +92,10 @@ public class InstallationPathConfig {
|
|||||||
return CONFIG_PATH;
|
return CONFIG_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPipelinePath() {
|
||||||
|
return PIPELINE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getCustomFilesPath() {
|
public static String getCustomFilesPath() {
|
||||||
return CUSTOM_FILES_PATH;
|
return CUSTOM_FILES_PATH;
|
||||||
}
|
}
|
||||||
@@ -100,6 +112,18 @@ public class InstallationPathConfig {
|
|||||||
return CUSTOM_SETTINGS_PATH;
|
return CUSTOM_SETTINGS_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPipelineWatchedFoldersDir() {
|
||||||
|
return PIPELINE_WATCHED_FOLDERS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelineFinishedFoldersDir() {
|
||||||
|
return PIPELINE_FINISHED_FOLDERS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPipelineDefaultWebUIConfigsDir() {
|
||||||
|
return PIPELINE_DEFAULT_WEB_UI_CONFIGS;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticPath() {
|
public static String getStaticPath() {
|
||||||
return STATIC_PATH;
|
return STATIC_PATH;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import jakarta.servlet.ServletException;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ import com.posthog.java.PostHog;
|
|||||||
|
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
|
||||||
public class PostHogConfig {
|
public class PostHogConfig {
|
||||||
|
|
||||||
@Value("${posthog.api.key}")
|
@Value("${posthog.api.key}")
|
||||||
@@ -24,11 +21,7 @@ public class PostHogConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PostHog postHogClient() {
|
public PostHog postHogClient() {
|
||||||
postHogClient =
|
postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build();
|
||||||
new PostHog.Builder(posthogApiKey)
|
|
||||||
.host(posthogHost)
|
|
||||||
.logger(new PostHogLoggerImpl())
|
|
||||||
.build();
|
|
||||||
return postHogClient;
|
return postHogClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package stirling.software.SPDF.config;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.posthog.java.PostHogLogger;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class PostHogLoggerImpl implements PostHogLogger {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void debug(String message) {
|
|
||||||
log.debug(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void info(String message) {
|
|
||||||
log.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void warn(String message) {
|
|
||||||
log.warn(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void error(String message) {
|
|
||||||
log.error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void error(String message, Throwable throwable) {
|
|
||||||
if (message.contains("Error sending events to PostHog")) {
|
|
||||||
log.warn(
|
|
||||||
"Error sending metrics, Likely caused by no internet connection. Non Blocking");
|
|
||||||
} else {
|
|
||||||
log.error(message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package stirling.software.SPDF.config;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Configuration
|
|
||||||
@Getter
|
|
||||||
public class RuntimePathConfig {
|
|
||||||
private final ApplicationProperties properties;
|
|
||||||
private final String basePath;
|
|
||||||
private final String weasyPrintPath;
|
|
||||||
private final String unoConvertPath;
|
|
||||||
|
|
||||||
// Pipeline paths
|
|
||||||
private final String pipelineWatchedFoldersPath;
|
|
||||||
private final String pipelineFinishedFoldersPath;
|
|
||||||
private final String pipelineDefaultWebUiConfigs;
|
|
||||||
private final String pipelinePath;
|
|
||||||
|
|
||||||
public RuntimePathConfig(ApplicationProperties properties) {
|
|
||||||
this.properties = properties;
|
|
||||||
this.basePath = InstallationPathConfig.getPath();
|
|
||||||
|
|
||||||
this.pipelinePath = Path.of(basePath, "pipeline").toString();
|
|
||||||
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
|
||||||
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
|
||||||
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
|
||||||
|
|
||||||
Pipeline pipeline = properties.getSystem().getCustomPaths().getPipeline();
|
|
||||||
|
|
||||||
this.pipelineWatchedFoldersPath =
|
|
||||||
resolvePath(
|
|
||||||
defaultWatchedFolders,
|
|
||||||
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
|
|
||||||
this.pipelineFinishedFoldersPath =
|
|
||||||
resolvePath(
|
|
||||||
defaultFinishedFolders,
|
|
||||||
pipeline != null ? pipeline.getFinishedFoldersDir() : null);
|
|
||||||
this.pipelineDefaultWebUiConfigs =
|
|
||||||
resolvePath(
|
|
||||||
defaultWebUIConfigs,
|
|
||||||
pipeline != null ? pipeline.getWebUIConfigsDir() : null);
|
|
||||||
|
|
||||||
boolean isDocker = isRunningInDocker();
|
|
||||||
|
|
||||||
// Initialize Operation paths
|
|
||||||
String defaultWeasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
|
|
||||||
String defaultUnoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
|
|
||||||
|
|
||||||
Operations operations = properties.getSystem().getCustomPaths().getOperations();
|
|
||||||
this.weasyPrintPath =
|
|
||||||
resolvePath(
|
|
||||||
defaultWeasyPrintPath,
|
|
||||||
operations != null ? operations.getWeasyprint() : null);
|
|
||||||
this.unoConvertPath =
|
|
||||||
resolvePath(
|
|
||||||
defaultUnoConvertPath,
|
|
||||||
operations != null ? operations.getUnoconvert() : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolvePath(String defaultPath, String customPath) {
|
|
||||||
return StringUtils.isNotBlank(customPath) ? customPath : defaultPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRunningInDocker() {
|
|
||||||
return Files.exists(Path.of("/.dockerenv"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
package stirling.software.SPDF.config;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.snakeyaml.engine.v2.api.Dump;
|
|
||||||
import org.snakeyaml.engine.v2.api.DumpSettings;
|
|
||||||
import org.snakeyaml.engine.v2.api.LoadSettings;
|
|
||||||
import org.snakeyaml.engine.v2.api.StreamDataWriter;
|
|
||||||
import org.snakeyaml.engine.v2.common.FlowStyle;
|
|
||||||
import org.snakeyaml.engine.v2.common.ScalarStyle;
|
|
||||||
import org.snakeyaml.engine.v2.composer.Composer;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.MappingNode;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.Node;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.NodeTuple;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.ScalarNode;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.SequenceNode;
|
|
||||||
import org.snakeyaml.engine.v2.nodes.Tag;
|
|
||||||
import org.snakeyaml.engine.v2.parser.ParserImpl;
|
|
||||||
import org.snakeyaml.engine.v2.scanner.StreamReader;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class YamlHelper {
|
|
||||||
|
|
||||||
// YAML dump settings with comment support and block flow style
|
|
||||||
private static final DumpSettings DUMP_SETTINGS =
|
|
||||||
DumpSettings.builder()
|
|
||||||
.setDumpComments(true)
|
|
||||||
.setWidth(Integer.MAX_VALUE)
|
|
||||||
.setDefaultFlowStyle(FlowStyle.BLOCK)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final String yamlContent; // Stores the entire YAML content as a string
|
|
||||||
|
|
||||||
private LoadSettings loadSettings =
|
|
||||||
LoadSettings.builder()
|
|
||||||
.setUseMarks(true)
|
|
||||||
.setMaxAliasesForCollections(Integer.MAX_VALUE)
|
|
||||||
.setAllowRecursiveKeys(true)
|
|
||||||
.setParseComments(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private Path originalFilePath;
|
|
||||||
private Node updatedRootNode;
|
|
||||||
|
|
||||||
// Constructor with custom LoadSettings and YAML string
|
|
||||||
public YamlHelper(LoadSettings loadSettings, String yamlContent) {
|
|
||||||
this.loadSettings = loadSettings;
|
|
||||||
this.yamlContent = yamlContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor that reads YAML from a file path
|
|
||||||
public YamlHelper(Path originalFilePath) throws IOException {
|
|
||||||
this.yamlContent = Files.readString(originalFilePath);
|
|
||||||
this.originalFilePath = originalFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates values in the target YAML based on values from the source YAML. It ensures that only
|
|
||||||
* existing keys in the target YAML are updated.
|
|
||||||
*
|
|
||||||
* @return true if at least one key was updated, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean updateValuesFromYaml(YamlHelper sourceYaml, YamlHelper targetYaml) {
|
|
||||||
boolean updated = false;
|
|
||||||
Set<String> sourceKeys = sourceYaml.getAllKeys();
|
|
||||||
Set<String> targetKeys = targetYaml.getAllKeys();
|
|
||||||
|
|
||||||
for (String key : sourceKeys) {
|
|
||||||
String[] keyArray = key.split("\\.");
|
|
||||||
|
|
||||||
Object newValue = sourceYaml.getValueByExactKeyPath(keyArray);
|
|
||||||
Object currentValue = targetYaml.getValueByExactKeyPath(keyArray);
|
|
||||||
if (newValue != null
|
|
||||||
&& (!newValue.equals(currentValue) || !sourceKeys.equals(targetKeys))) {
|
|
||||||
boolean updatedKey = targetYaml.updateValue(Arrays.asList(keyArray), newValue);
|
|
||||||
if (updatedKey) updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a value in the YAML structure.
|
|
||||||
*
|
|
||||||
* @param keys The hierarchical keys leading to the value.
|
|
||||||
* @param newValue The new value to set.
|
|
||||||
* @return true if the value was updated, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean updateValue(List<String> keys, Object newValue) {
|
|
||||||
return updateValue(getRootNode(), keys, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean updateValue(Node node, List<String> keys, Object newValue) {
|
|
||||||
if (!(node instanceof MappingNode mappingNode)) return false;
|
|
||||||
|
|
||||||
List<NodeTuple> updatedTuples = new ArrayList<>();
|
|
||||||
boolean updated = false;
|
|
||||||
|
|
||||||
for (NodeTuple tuple : mappingNode.getValue()) {
|
|
||||||
ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;
|
|
||||||
if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {
|
|
||||||
updatedTuples.add(tuple);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node valueNode = tuple.getValueNode();
|
|
||||||
|
|
||||||
if (keys.size() == 1) {
|
|
||||||
Tag tag = valueNode.getTag();
|
|
||||||
Node newValueNode = null;
|
|
||||||
|
|
||||||
if (isAnyInteger(newValue)) {
|
|
||||||
newValueNode =
|
|
||||||
new ScalarNode(Tag.INT, String.valueOf(newValue), ScalarStyle.PLAIN);
|
|
||||||
} else if (isFloat(newValue)) {
|
|
||||||
Object floatValue = Float.valueOf(String.valueOf(newValue));
|
|
||||||
newValueNode =
|
|
||||||
new ScalarNode(
|
|
||||||
Tag.FLOAT, String.valueOf(floatValue), ScalarStyle.PLAIN);
|
|
||||||
} else if ("true".equals(newValue) || "false".equals(newValue)) {
|
|
||||||
newValueNode =
|
|
||||||
new ScalarNode(Tag.BOOL, String.valueOf(newValue), ScalarStyle.PLAIN);
|
|
||||||
} else if (newValue instanceof List<?> list) {
|
|
||||||
List<Node> sequenceNodes = new ArrayList<>();
|
|
||||||
for (Object item : list) {
|
|
||||||
Object obj = String.valueOf(item);
|
|
||||||
if (isAnyInteger(item)) {
|
|
||||||
tag = Tag.INT;
|
|
||||||
} else if (isFloat(item)) {
|
|
||||||
obj = Float.valueOf(String.valueOf(item));
|
|
||||||
tag = Tag.FLOAT;
|
|
||||||
} else if ("true".equals(item) || "false".equals(item)) {
|
|
||||||
tag = Tag.BOOL;
|
|
||||||
} else if (item == null || "null".equals(item)) {
|
|
||||||
tag = Tag.NULL;
|
|
||||||
} else {
|
|
||||||
tag = Tag.STR;
|
|
||||||
}
|
|
||||||
sequenceNodes.add(
|
|
||||||
new ScalarNode(tag, String.valueOf(obj), ScalarStyle.PLAIN));
|
|
||||||
}
|
|
||||||
newValueNode = new SequenceNode(Tag.SEQ, sequenceNodes, FlowStyle.FLOW);
|
|
||||||
} else if (tag == Tag.NULL) {
|
|
||||||
if ("true".equals(newValue)
|
|
||||||
|| "false".equals(newValue)
|
|
||||||
|| newValue instanceof Boolean) {
|
|
||||||
tag = Tag.BOOL;
|
|
||||||
}
|
|
||||||
newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);
|
|
||||||
} else {
|
|
||||||
newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);
|
|
||||||
}
|
|
||||||
copyComments(valueNode, newValueNode);
|
|
||||||
|
|
||||||
updatedTuples.add(new NodeTuple(keyNode, newValueNode));
|
|
||||||
updated = true;
|
|
||||||
} else if (valueNode instanceof MappingNode) {
|
|
||||||
updated = updateValue(valueNode, keys.subList(1, keys.size()), newValue);
|
|
||||||
updatedTuples.add(tuple);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
mappingNode.getValue().clear();
|
|
||||||
mappingNode.getValue().addAll(updatedTuples);
|
|
||||||
}
|
|
||||||
setNewNode(node);
|
|
||||||
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a value based on an exact key path.
|
|
||||||
*
|
|
||||||
* @param keys The key hierarchy leading to the value.
|
|
||||||
* @return The value if found, otherwise null.
|
|
||||||
*/
|
|
||||||
public Object getValueByExactKeyPath(String... keys) {
|
|
||||||
return getValueByExactKeyPath(getRootNode(), new ArrayDeque<>(List.of(keys)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object getValueByExactKeyPath(Node node, Deque<String> keyQueue) {
|
|
||||||
if (!(node instanceof MappingNode mappingNode)) return null;
|
|
||||||
|
|
||||||
String currentKey = keyQueue.poll();
|
|
||||||
if (currentKey == null) return null;
|
|
||||||
|
|
||||||
for (NodeTuple tuple : mappingNode.getValue()) {
|
|
||||||
if (tuple.getKeyNode() instanceof ScalarNode keyNode
|
|
||||||
&& keyNode.getValue().equals(currentKey)) {
|
|
||||||
if (keyQueue.isEmpty()) {
|
|
||||||
Node valueNode = tuple.getValueNode();
|
|
||||||
|
|
||||||
if (valueNode instanceof ScalarNode scalarValueNode) {
|
|
||||||
return scalarValueNode.getValue();
|
|
||||||
} else if (valueNode instanceof MappingNode subMapping) {
|
|
||||||
return getValueByExactKeyPath(subMapping, keyQueue);
|
|
||||||
} else if (valueNode instanceof SequenceNode sequenceNode) {
|
|
||||||
List<Object> valuesList = new ArrayList<>();
|
|
||||||
for (Node o : sequenceNode.getValue()) {
|
|
||||||
if (o instanceof ScalarNode scalarValue) {
|
|
||||||
valuesList.add(scalarValue.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return valuesList;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getValueByExactKeyPath(tuple.getValueNode(), keyQueue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> cachedKeys;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the set of all keys present in the YAML structure. Keys are returned as
|
|
||||||
* dot-separated paths for nested keys.
|
|
||||||
*
|
|
||||||
* @return A set containing all keys in dot notation.
|
|
||||||
*/
|
|
||||||
public Set<String> getAllKeys() {
|
|
||||||
if (cachedKeys == null) {
|
|
||||||
cachedKeys = getAllKeys(getRootNode());
|
|
||||||
}
|
|
||||||
return cachedKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects all keys from the YAML node recursively.
|
|
||||||
*
|
|
||||||
* @param node The current YAML node.
|
|
||||||
* @param currentPath The accumulated path of keys.
|
|
||||||
* @param allKeys The set storing all collected keys.
|
|
||||||
*/
|
|
||||||
private Set<String> getAllKeys(Node node) {
|
|
||||||
Set<String> allKeys = new LinkedHashSet<>();
|
|
||||||
collectKeys(node, "", allKeys);
|
|
||||||
return allKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively traverses the YAML structure to collect all keys.
|
|
||||||
*
|
|
||||||
* @param node The current node in the YAML structure.
|
|
||||||
* @param currentPath The accumulated key path.
|
|
||||||
* @param allKeys The set storing collected keys.
|
|
||||||
*/
|
|
||||||
private void collectKeys(Node node, String currentPath, Set<String> allKeys) {
|
|
||||||
if (node instanceof MappingNode mappingNode) {
|
|
||||||
for (NodeTuple tuple : mappingNode.getValue()) {
|
|
||||||
if (tuple.getKeyNode() instanceof ScalarNode keyNode) {
|
|
||||||
String newPath =
|
|
||||||
currentPath.isEmpty()
|
|
||||||
? keyNode.getValue()
|
|
||||||
: currentPath + "." + keyNode.getValue();
|
|
||||||
allKeys.add(newPath);
|
|
||||||
collectKeys(tuple.getValueNode(), newPath, allKeys);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the root node of the YAML document. If a new node was previously set, it is
|
|
||||||
* returned instead.
|
|
||||||
*
|
|
||||||
* @return The root node of the YAML structure.
|
|
||||||
*/
|
|
||||||
private Node getRootNode() {
|
|
||||||
if (this.updatedRootNode != null) {
|
|
||||||
return this.updatedRootNode;
|
|
||||||
}
|
|
||||||
Composer composer = new Composer(loadSettings, getParserImpl());
|
|
||||||
Optional<Node> rootNodeOpt = composer.getSingleNode();
|
|
||||||
if (rootNodeOpt.isPresent()) {
|
|
||||||
return rootNodeOpt.get();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new root node, allowing modifications to be tracked.
|
|
||||||
*
|
|
||||||
* @param newRootNode The modified root node.
|
|
||||||
*/
|
|
||||||
public void setNewNode(Node newRootNode) {
|
|
||||||
this.updatedRootNode = newRootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the current root node (either the original or the updated one).
|
|
||||||
*
|
|
||||||
* @return The root node.
|
|
||||||
*/
|
|
||||||
public Node getUpdatedRootNode() {
|
|
||||||
if (this.updatedRootNode == null) {
|
|
||||||
this.updatedRootNode = getRootNode();
|
|
||||||
}
|
|
||||||
return this.updatedRootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the YAML parser.
|
|
||||||
*
|
|
||||||
* @return The configured parser.
|
|
||||||
*/
|
|
||||||
private ParserImpl getParserImpl() {
|
|
||||||
return new ParserImpl(loadSettings, getStreamReader());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a stream reader for the YAML content.
|
|
||||||
*
|
|
||||||
* @return The configured stream reader.
|
|
||||||
*/
|
|
||||||
private StreamReader getStreamReader() {
|
|
||||||
return new StreamReader(loadSettings, yamlContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MappingNode save(Path saveFilePath) throws IOException {
|
|
||||||
if (!saveFilePath.equals(originalFilePath)) {
|
|
||||||
Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));
|
|
||||||
}
|
|
||||||
return (MappingNode) getUpdatedRootNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveOverride(Path saveFilePath) throws IOException {
|
|
||||||
Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a YAML node back to a YAML-formatted string.
|
|
||||||
*
|
|
||||||
* @param rootNode The root node to be converted.
|
|
||||||
* @return A YAML-formatted string.
|
|
||||||
*/
|
|
||||||
public String convertNodeToYaml(Node rootNode) {
|
|
||||||
StringWriter writer = new StringWriter();
|
|
||||||
StreamDataWriter streamDataWriter =
|
|
||||||
new StreamDataWriter() {
|
|
||||||
@Override
|
|
||||||
public void write(String str) {
|
|
||||||
writer.write(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(String str, int off, int len) {
|
|
||||||
writer.write(str, off, len);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new Dump(DUMP_SETTINGS).dumpNode(rootNode, streamDataWriter);
|
|
||||||
return writer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isParsable(String value, Function<String, ?> parser) {
|
|
||||||
try {
|
|
||||||
parser.apply(value);
|
|
||||||
return true;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given object is an integer.
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents an integer, false otherwise.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
|
||||||
public static boolean isInteger(Object object) {
|
|
||||||
if (object instanceof Integer
|
|
||||||
|| object instanceof Short
|
|
||||||
|| object instanceof Byte
|
|
||||||
|| object instanceof Long) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (object instanceof String str) {
|
|
||||||
return isParsable(str, Integer::parseInt);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given object is a floating-point number.
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents a float, false otherwise.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
|
||||||
public static boolean isFloat(Object object) {
|
|
||||||
return (object instanceof Float || object instanceof Double)
|
|
||||||
|| (object instanceof String str && isParsable(str, Float::parseFloat));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given object is a short integer.
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents a short integer, false otherwise.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
|
||||||
public static boolean isShort(Object object) {
|
|
||||||
return (object instanceof Long)
|
|
||||||
|| (object instanceof String str && isParsable(str, Short::parseShort));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given object is a byte.
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents a byte, false otherwise.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
|
||||||
public static boolean isByte(Object object) {
|
|
||||||
return (object instanceof Long)
|
|
||||||
|| (object instanceof String str && isParsable(str, Byte::parseByte));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given object is a long integer.
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents a long integer, false otherwise.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
|
||||||
public static boolean isLong(Object object) {
|
|
||||||
return (object instanceof Long)
|
|
||||||
|| (object instanceof String str && isParsable(str, Long::parseLong));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if an object is any type of integer (short, byte, long, or int).
|
|
||||||
*
|
|
||||||
* @param object The object to check.
|
|
||||||
* @return True if the object represents an integer type, false otherwise.
|
|
||||||
*/
|
|
||||||
public static boolean isAnyInteger(Object object) {
|
|
||||||
return isInteger(object) || isShort(object) || isByte(object) || isLong(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies comments from an old node to a new one.
|
|
||||||
*
|
|
||||||
* @param oldNode The original node with comments.
|
|
||||||
* @param newValueNode The new node to which comments should be copied.
|
|
||||||
*/
|
|
||||||
private void copyComments(Node oldNode, Node newValueNode) {
|
|
||||||
if (oldNode == null || newValueNode == null) return;
|
|
||||||
if (oldNode.getBlockComments() != null) {
|
|
||||||
newValueNode.setBlockComments(oldNode.getBlockComments());
|
|
||||||
}
|
|
||||||
if (oldNode.getInLineComments() != null) {
|
|
||||||
newValueNode.setInLineComments(oldNode.getInLineComments());
|
|
||||||
}
|
|
||||||
if (oldNode.getEndComments() != null) {
|
|
||||||
newValueNode.setEndComments(oldNode.getEndComments());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package stirling.software.SPDF.config.interfaces;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
public interface DatabaseInterface {
|
public interface DatabaseInterface {
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -69,7 +67,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
|
|||||||
}
|
}
|
||||||
if (exception instanceof BadCredentialsException
|
if (exception instanceof BadCredentialsException
|
||||||
|| exception instanceof UsernameNotFoundException) {
|
|| exception instanceof UsernameNotFoundException) {
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof InternalAuthenticationServiceException
|
if (exception instanceof InternalAuthenticationServiceException
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import jakarta.servlet.ServletException;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -14,75 +14,91 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
|
|||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import com.coveo.saml.SamlClient;
|
import com.coveo.saml.SamlClient;
|
||||||
import com.coveo.saml.SamlException;
|
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.SPDFApplication;
|
import stirling.software.SPDF.SPDFApplication;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.Provider;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
import stirling.software.SPDF.utils.UrlUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
public static final String LOGOUT_PATH = "/login?logout=true";
|
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLogoutSuccess(
|
public void onLogoutSuccess(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
if (!response.isCommitted()) {
|
if (!response.isCommitted()) {
|
||||||
|
// Handle user logout due to disabled account
|
||||||
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle OAuth2 authentication error
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
if (authentication instanceof Saml2Authentication samlAuthentication) {
|
// Handle SAML2 logout redirection
|
||||||
// Handle SAML2 logout redirection
|
if (authentication instanceof Saml2Authentication) {
|
||||||
getRedirect_saml2(request, response, samlAuthentication);
|
getRedirect_saml2(request, response, authentication);
|
||||||
} else if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {
|
return;
|
||||||
// Handle OAuth2 logout redirection
|
}
|
||||||
getRedirect_oauth2(request, response, oAuthToken);
|
// Handle OAuth2 logout redirection
|
||||||
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
// Handle Username/Password logout
|
getRedirect_oauth2(request, response, authentication);
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
return;
|
||||||
} else {
|
}
|
||||||
// Handle unknown authentication types
|
// Handle Username/Password logout
|
||||||
|
else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle unknown authentication types
|
||||||
|
else {
|
||||||
log.error(
|
log.error(
|
||||||
"Authentication class unknown: {}",
|
"authentication class unknown: "
|
||||||
authentication.getClass().getSimpleName());
|
+ authentication.getClass().getSimpleName());
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Redirect to login page after logout
|
// Redirect to login page after logout
|
||||||
String path = checkForErrors(request);
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
getRedirectStrategy().sendRedirect(request, response, path);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect for SAML2 authentication logout
|
// Redirect for SAML2 authentication logout
|
||||||
private void getRedirect_saml2(
|
private void getRedirect_saml2(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
HttpServletResponse response,
|
|
||||||
Saml2Authentication samlAuthentication)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
String registrationId = samlConf.getRegistrationId();
|
String registrationId = samlConf.getRegistrationId();
|
||||||
|
|
||||||
|
Saml2Authentication samlAuthentication = (Saml2Authentication) authentication;
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
CustomSaml2AuthenticatedPrincipal principal =
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
||||||
|
|
||||||
String nameIdValue = principal.name();
|
String nameIdValue = principal.getName();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read certificate from the resource
|
// Read certificate from the resource
|
||||||
@@ -93,7 +109,27 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
certificates.add(certificate);
|
certificates.add(certificate);
|
||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
// Construct URLs required for SAML configuration
|
||||||
SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates);
|
String serverUrl =
|
||||||
|
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
|
||||||
|
|
||||||
|
String relyingPartyIdentifier =
|
||||||
|
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
||||||
|
|
||||||
|
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
||||||
|
|
||||||
|
String idpUrl = samlConf.getIdpSingleLogoutUrl();
|
||||||
|
|
||||||
|
String idpIssuer = samlConf.getIdpIssuer();
|
||||||
|
|
||||||
|
// Create SamlClient instance for SAML logout
|
||||||
|
SamlClient samlClient =
|
||||||
|
new SamlClient(
|
||||||
|
relyingPartyIdentifier,
|
||||||
|
assertionConsumerServiceUrl,
|
||||||
|
idpUrl,
|
||||||
|
idpIssuer,
|
||||||
|
certificates,
|
||||||
|
SamlClient.SamlIdpBinding.POST);
|
||||||
|
|
||||||
// Read private key for service provider
|
// Read private key for service provider
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
@@ -105,134 +141,96 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
// Redirect to identity provider for logout
|
// Redirect to identity provider for logout
|
||||||
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(
|
log.error(nameIdValue, e);
|
||||||
"Error retrieving logout URL from Provider {} for user {}",
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
samlConf.getProvider(),
|
|
||||||
nameIdValue,
|
|
||||||
e);
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect for OAuth2 authentication logout
|
// Redirect for OAuth2 authentication logout
|
||||||
private void getRedirect_oauth2(
|
private void getRedirect_oauth2(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
HttpServletResponse response,
|
|
||||||
OAuth2AuthenticationToken oAuthToken)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String registrationId;
|
String param = "logout=true";
|
||||||
|
String registrationId = null;
|
||||||
|
String issuer = null;
|
||||||
|
String clientId = null;
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
String path = checkForErrors(request);
|
|
||||||
|
|
||||||
String redirectUrl = UrlUtils.getOrigin(request) + "/login?" + path;
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
registrationId = oAuthToken.getAuthorizedClientRegistrationId();
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
|
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get OAuth2 provider details from configuration
|
||||||
|
Provider provider = oauth.getClient().get(registrationId);
|
||||||
|
issuer = provider.getIssuer();
|
||||||
|
clientId = provider.getClientId();
|
||||||
|
} catch (UnsupportedProviderException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||||
|
issuer = oauth.getIssuer();
|
||||||
|
clientId = oauth.getClientId();
|
||||||
|
}
|
||||||
|
String errorMessage = "";
|
||||||
|
// Handle different error scenarios during logout
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||||
|
} else if ((errorMessage = request.getParameter("error")) != null) {
|
||||||
|
param = "error=" + sanitizeInput(errorMessage);
|
||||||
|
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
||||||
|
param = "erroroauth=" + sanitizeInput(errorMessage);
|
||||||
|
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
||||||
|
param = "error=oauth2AutoCreateDisabled";
|
||||||
|
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
||||||
|
param = "erroroauth=oauth2_admin_blocked_user";
|
||||||
|
} else if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
param = "erroroauth=userIsDisabled";
|
||||||
|
} else if (request.getParameter("badcredentials") != null) {
|
||||||
|
param = "error=badcredentials";
|
||||||
|
}
|
||||||
|
|
||||||
|
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
// Redirect based on OAuth2 provider
|
||||||
switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase()) {
|
||||||
case "keycloak" -> {
|
case "keycloak":
|
||||||
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
// Add Keycloak specific logout URL if needed
|
||||||
|
String logoutUrl =
|
||||||
boolean isKeycloak = !keycloak.getIssuer().isBlank();
|
issuer
|
||||||
boolean isCustomOAuth = !oauth.getIssuer().isBlank();
|
+ "/protocol/openid-connect/logout"
|
||||||
|
+ "?client_id="
|
||||||
String logoutUrl = redirectUrl;
|
+ clientId
|
||||||
|
+ "&post_logout_redirect_uri="
|
||||||
if (isKeycloak) {
|
+ response.encodeRedirectURL(redirect_url);
|
||||||
logoutUrl = keycloak.getIssuer();
|
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||||
} else if (isCustomOAuth) {
|
|
||||||
logoutUrl = oauth.getIssuer();
|
|
||||||
}
|
|
||||||
if (isKeycloak || isCustomOAuth) {
|
|
||||||
logoutUrl +=
|
|
||||||
"/protocol/openid-connect/logout"
|
|
||||||
+ "?client_id="
|
|
||||||
+ oauth.getClientId()
|
|
||||||
+ "&post_logout_redirect_uri="
|
|
||||||
+ response.encodeRedirectURL(redirectUrl);
|
|
||||||
log.info("Redirecting to Keycloak logout URL: {}", logoutUrl);
|
|
||||||
} else {
|
|
||||||
log.info(
|
|
||||||
"No redirect URL for {} available. Redirecting to default logout URL: {}",
|
|
||||||
registrationId,
|
|
||||||
logoutUrl);
|
|
||||||
}
|
|
||||||
response.sendRedirect(logoutUrl);
|
response.sendRedirect(logoutUrl);
|
||||||
}
|
break;
|
||||||
case "github", "google" -> {
|
case "github":
|
||||||
log.info(
|
// Add GitHub specific logout URL if needed
|
||||||
"No redirect URL for {} available. Redirecting to default logout URL: {}",
|
String githubLogoutUrl = "https://github.com/logout";
|
||||||
registrationId,
|
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||||
redirectUrl);
|
response.sendRedirect(githubLogoutUrl);
|
||||||
response.sendRedirect(redirectUrl);
|
break;
|
||||||
}
|
case "google":
|
||||||
default -> {
|
// Add Google specific logout URL if needed
|
||||||
log.info("Redirecting to default logout URL: {}", redirectUrl);
|
// String googleLogoutUrl =
|
||||||
response.sendRedirect(redirectUrl);
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
}
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Google does not have a specific logout URL");
|
||||||
|
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
|
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
||||||
|
response.sendRedirect(defaultRedirectUrl);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SamlClient getSamlClient(
|
// Sanitize input to avoid potential security vulnerabilities
|
||||||
String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
|
|
||||||
throws SamlException {
|
|
||||||
String serverUrl =
|
|
||||||
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
|
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
|
||||||
|
|
||||||
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
|
||||||
|
|
||||||
String idpSLOUrl = samlConf.getIdpSingleLogoutUrl();
|
|
||||||
|
|
||||||
String idpIssuer = samlConf.getIdpIssuer();
|
|
||||||
|
|
||||||
// Create SamlClient instance for SAML logout
|
|
||||||
return new SamlClient(
|
|
||||||
relyingPartyIdentifier,
|
|
||||||
assertionConsumerServiceUrl,
|
|
||||||
idpSLOUrl,
|
|
||||||
idpIssuer,
|
|
||||||
certificates,
|
|
||||||
SamlClient.SamlIdpBinding.POST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles different error scenarios during logout. Will return a <code>String</code> containing
|
|
||||||
* the error request parameter.
|
|
||||||
*
|
|
||||||
* @param request the user's <code>HttpServletRequest</code> request.
|
|
||||||
* @return a <code>String</code> containing the error request parameter.
|
|
||||||
*/
|
|
||||||
private String checkForErrors(HttpServletRequest request) {
|
|
||||||
String errorMessage;
|
|
||||||
String path = "logout=true";
|
|
||||||
|
|
||||||
if (request.getParameter("oAuth2AuthenticationErrorWeb") != null) {
|
|
||||||
path = "errorOAuth=userAlreadyExistsWeb";
|
|
||||||
} else if ((errorMessage = request.getParameter("errorOAuth")) != null) {
|
|
||||||
path = "errorOAuth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("oAuth2AutoCreateDisabled") != null) {
|
|
||||||
path = "errorOAuth=oAuth2AutoCreateDisabled";
|
|
||||||
} else if (request.getParameter("oAuth2AdminBlockedUser") != null) {
|
|
||||||
path = "errorOAuth=oAuth2AdminBlockedUser";
|
|
||||||
} else if (request.getParameter("userIsDisabled") != null) {
|
|
||||||
path = "errorOAuth=userIsDisabled";
|
|
||||||
} else if ((errorMessage = request.getParameter("error")) != null) {
|
|
||||||
path = "errorOAuth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("badCredentials") != null) {
|
|
||||||
path = "errorOAuth=badCredentials";
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize input to avoid potential security vulnerabilities. Will return a sanitised <code>
|
|
||||||
* String</code>.
|
|
||||||
*
|
|
||||||
* @return a sanitised <code>String</code>
|
|
||||||
*/
|
|
||||||
private String sanitizeInput(String input) {
|
private String sanitizeInput(String input) {
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ import jakarta.servlet.ServletException;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
import jakarta.servlet.*;
|
import jakarta.servlet.*;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
public class IPRateLimitingFilter implements Filter {
|
public class IPRateLimitingFilter implements Filter {
|
||||||
@@ -25,8 +24,8 @@ public class IPRateLimitingFilter implements Filter {
|
|||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
if (request instanceof HttpServletRequest httpServletRequest) {
|
if (request instanceof HttpServletRequest) {
|
||||||
HttpServletRequest httpRequest = httpServletRequest;
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
String method = httpRequest.getMethod();
|
String method = httpRequest.getMethod();
|
||||||
String requestURI = httpRequest.getRequestURI();
|
String requestURI = httpRequest.getRequestURI();
|
||||||
// Check if the request is for static resources
|
// Check if the request is for static resources
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import java.util.UUID;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -36,13 +34,12 @@ public class InitialSecuritySetup {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
try {
|
try {
|
||||||
|
if (databaseService.hasBackup()) {
|
||||||
|
databaseService.importDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
if (!userService.hasUsers()) {
|
if (!userService.hasUsers()) {
|
||||||
if (databaseService.hasBackup()) {
|
initializeAdminUser();
|
||||||
databaseService.importDatabase();
|
|
||||||
} else {
|
|
||||||
initializeAdminUser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userService.migrateOauth2ToSSO();
|
userService.migrateOauth2ToSSO();
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.AttemptCounter;
|
import stirling.software.SPDF.model.AttemptCounter;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -29,7 +29,6 @@ import org.springframework.security.web.savedrequest.NullRequestCache;
|
|||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
@@ -51,7 +50,11 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Qualifier("loginEnabled")
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Qualifier("runningEE")
|
||||||
private final boolean runningEE;
|
private final boolean runningEE;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@@ -105,7 +108,6 @@ public class SecurityConfiguration {
|
|||||||
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@@ -161,7 +163,8 @@ public class SecurityConfiguration {
|
|||||||
.logoutSuccessHandler(
|
.logoutSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
new CustomLogoutSuccessHandler(applicationProperties))
|
||||||
.clearAuthentication(true)
|
.clearAuthentication(true)
|
||||||
.invalidateHttpSession(true)
|
.invalidateHttpSession( // Invalidate session
|
||||||
|
true)
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
.deleteCookies("JSESSIONID", "remember-me"));
|
||||||
http.rememberMe(
|
http.rememberMe(
|
||||||
rememberMeConfigurer -> // Use the configurator directly
|
rememberMeConfigurer -> // Use the configurator directly
|
||||||
@@ -223,14 +226,14 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Active()) {
|
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
oauth2.loginPage("/oauth2")
|
oauth2.loginPage("/oauth2")
|
||||||
.
|
.
|
||||||
/*
|
/*
|
||||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
||||||
If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser'
|
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
||||||
is set as true, else login fails with an error message advising the same.
|
is set as true, else login fails with an error message advising the same.
|
||||||
*/
|
*/
|
||||||
successHandler(
|
successHandler(
|
||||||
@@ -254,7 +257,8 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
|
if (applicationProperties.getSecurity().isSaml2Activ()) {
|
||||||
|
// && runningEE
|
||||||
// Configure the authentication provider
|
// Configure the authentication provider
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
@@ -279,13 +283,12 @@ public class SecurityConfiguration {
|
|||||||
.authenticationRequestResolver(
|
.authenticationRequestResolver(
|
||||||
saml2AuthenticationRequestResolver);
|
saml2AuthenticationRequestResolver);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error configuring SAML 2 login", e);
|
log.error("Error configuring SAML2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug("SAML 2 login is not enabled. Using default.");
|
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -311,7 +314,7 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public boolean activeSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
@@ -88,7 +86,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
// provider for API keys.
|
// provider for API keys.
|
||||||
Optional<User> user = userService.getUserByApiKey(apiKey);
|
Optional<User> user = userService.getUserByApiKey(apiKey);
|
||||||
if (user.isEmpty()) {
|
if (!user.isPresent()) {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter().write("Invalid API Key.");
|
response.getWriter().write("Invalid API Key.");
|
||||||
return;
|
return;
|
||||||
@@ -123,11 +121,9 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request"
|
"Authentication required. Please provide a X-API-KEY in request header.\n"
|
||||||
+ " header.\n"
|
|
||||||
+ "This is found in Settings -> Account Settings -> API Key\n"
|
+ "This is found in Settings -> Account Settings -> API Key\n"
|
||||||
+ "Alternatively you can disable authentication if this is"
|
+ "Alternatively you can disable authentication if this is unexpected");
|
||||||
+ " unexpected");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,21 +139,21 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Extract username and determine the login method
|
// Extract username and determine the login method
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
if (principal instanceof UserDetails detailsUser) {
|
if (principal instanceof UserDetails) {
|
||||||
username = detailsUser.getUsername();
|
username = ((UserDetails) principal).getUsername();
|
||||||
loginMethod = LoginMethod.USERDETAILS;
|
loginMethod = LoginMethod.USERDETAILS;
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
username = oAuth2User.getName();
|
username = ((OAuth2User) principal).getName();
|
||||||
loginMethod = LoginMethod.OAUTH2USER;
|
loginMethod = LoginMethod.OAUTH2USER;
|
||||||
OAUTH2 oAuth = securityProp.getOauth2();
|
OAUTH2 oAuth = securityProp.getOauth2();
|
||||||
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
username = saml2User.name();
|
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
loginMethod = LoginMethod.SAML2USER;
|
loginMethod = LoginMethod.SAML2USER;
|
||||||
SAML2 saml2 = securityProp.getSaml2();
|
SAML2 saml2 = securityProp.getSaml2();
|
||||||
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
||||||
} else if (principal instanceof String stringUser) {
|
} else if (principal instanceof String) {
|
||||||
username = stringUser;
|
username = (String) principal;
|
||||||
loginMethod = LoginMethod.STRINGUSER;
|
loginMethod = LoginMethod.STRINGUSER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,14 +168,14 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
boolean isUserDisabled = userService.isUserDisabled(username);
|
boolean isUserDisabled = userService.isUserDisabled(username);
|
||||||
|
|
||||||
boolean notSsoLogin =
|
boolean notSsoLogin =
|
||||||
!LoginMethod.OAUTH2USER.equals(loginMethod)
|
!loginMethod.equals(LoginMethod.OAUTH2USER)
|
||||||
&& !LoginMethod.SAML2USER.equals(loginMethod);
|
&& !loginMethod.equals(LoginMethod.SAML2USER);
|
||||||
|
|
||||||
// Block user registration if not allowed by configuration
|
// Block user registration if not allowed by configuration
|
||||||
if (blockRegistration && !isUserExists) {
|
if (blockRegistration && !isUserExists) {
|
||||||
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
request.getContextPath() + "/logout?oAuth2AdminBlockedUser=true");
|
request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +191,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Redirect to logout if credentials are invalid
|
// Redirect to logout if credentials are invalid
|
||||||
if (!isUserExists && notSsoLogin) {
|
if (!isUserExists && notSsoLogin) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?badCredentials=true");
|
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isUserDisabled) {
|
if (isUserDisabled) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -21,13 +21,12 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@@ -78,18 +77,20 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public void processSSOPostLogin(String username, boolean autoCreateUser)
|
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
||||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
if (existingUser.isPresent()) {
|
if (existingUser.isPresent()) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
if (autoCreateUser) {
|
if (autoCreateUser) {
|
||||||
saveUser(username, AuthenticationType.SSO);
|
saveUser(username, AuthenticationType.SSO);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Authentication getAuthentication(String apiKey) {
|
public Authentication getAuthentication(String apiKey) {
|
||||||
@@ -121,14 +122,12 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public User addApiKeyToUser(String username) {
|
public User addApiKeyToUser(String username) {
|
||||||
Optional<User> userOpt = findByUsernameIgnoreCase(username);
|
Optional<User> user = findByUsernameIgnoreCase(username);
|
||||||
User user = saveUser(userOpt, generateApiKey());
|
if (user.isPresent()) {
|
||||||
try {
|
user.get().setApiKey(generateApiKey());
|
||||||
databaseService.exportDatabase();
|
return userRepository.save(user.get());
|
||||||
} catch (SQLException | UnsupportedProviderException e) {
|
|
||||||
log.error("Error exporting database after adding API key to user", e);
|
|
||||||
}
|
}
|
||||||
return user;
|
throw new UsernameNotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
public User refreshApiKeyForUser(String username) {
|
public User refreshApiKeyForUser(String username) {
|
||||||
@@ -140,9 +139,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
User user =
|
User user =
|
||||||
findByUsernameIgnoreCase(username)
|
findByUsernameIgnoreCase(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
if (user.getApiKey() == null || user.getApiKey().length() == 0) {
|
|
||||||
user = addApiKeyToUser(username);
|
|
||||||
}
|
|
||||||
return user.getApiKey();
|
return user.getApiKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +169,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
saveUser(username, authenticationType, Role.USER.getRoleId());
|
saveUser(username, authenticationType, Role.USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private User saveUser(Optional<User> user, String apiKey) {
|
|
||||||
if (user.isPresent()) {
|
|
||||||
user.get().setApiKey(apiKey);
|
|
||||||
return userRepository.save(user.get());
|
|
||||||
}
|
|
||||||
throw new UsernameNotFoundException("User not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
||||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
@@ -381,18 +369,21 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public void invalidateUserSessions(String username) {
|
public void invalidateUserSessions(String username) {
|
||||||
String usernameP = "";
|
String usernameP = "";
|
||||||
|
|
||||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||||
for (SessionInformation sessionsInformation :
|
for (SessionInformation sessionsInformation :
|
||||||
sessionRegistry.getAllSessions(principal, false)) {
|
sessionRegistry.getAllSessions(principal, false)) {
|
||||||
if (principal instanceof UserDetails detailsUser) {
|
if (principal instanceof UserDetails) {
|
||||||
usernameP = detailsUser.getUsername();
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
usernameP = userDetails.getUsername();
|
||||||
|
} else if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oAuth2User = (OAuth2User) principal;
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
usernameP = saml2User.name();
|
CustomSaml2AuthenticatedPrincipal saml2User =
|
||||||
} else if (principal instanceof String stringUser) {
|
(CustomSaml2AuthenticatedPrincipal) principal;
|
||||||
usernameP = stringUser;
|
usernameP = saml2User.getName();
|
||||||
|
} else if (principal instanceof String) {
|
||||||
|
usernameP = (String) principal;
|
||||||
}
|
}
|
||||||
if (usernameP.equalsIgnoreCase(username)) {
|
if (usernameP.equalsIgnoreCase(username)) {
|
||||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||||
@@ -403,56 +394,49 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public String getCurrentUsername() {
|
public String getCurrentUsername() {
|
||||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
if (principal instanceof UserDetails) {
|
||||||
if (principal instanceof UserDetails detailsUser) {
|
return ((UserDetails) principal).getUsername();
|
||||||
return detailsUser.getUsername();
|
} else if (principal instanceof OAuth2User) {
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
return ((OAuth2User) principal)
|
||||||
return oAuth2User.getAttribute(
|
.getAttribute(
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
return saml2User.name();
|
return ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
} else if (principal instanceof String stringUser) {
|
} else if (principal instanceof String) {
|
||||||
return stringUser;
|
return (String) principal;
|
||||||
|
} else {
|
||||||
|
return principal.toString();
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void syncCustomApiUser(String customApiKey) {
|
public void syncCustomApiUser(String customApiKey)
|
||||||
if (customApiKey == null || customApiKey.trim().isBlank()) {
|
throws SQLException, UnsupportedProviderException {
|
||||||
|
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = "CUSTOM_API_USER";
|
String username = "CUSTOM_API_USER";
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
|
if (!existingUser.isPresent()) {
|
||||||
existingUser.ifPresentOrElse(
|
// Create new user with API role
|
||||||
user -> {
|
User user = new User();
|
||||||
// Update API key if it has changed
|
user.setUsername(username);
|
||||||
User updatedUser = existingUser.get();
|
user.setPassword(UUID.randomUUID().toString());
|
||||||
|
user.setEnabled(true);
|
||||||
if (!customApiKey.equals(updatedUser.getApiKey())) {
|
user.setFirstLogin(false);
|
||||||
updatedUser.setApiKey(customApiKey);
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
userRepository.save(updatedUser);
|
user.setApiKey(customApiKey);
|
||||||
}
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
},
|
userRepository.save(user);
|
||||||
() -> {
|
|
||||||
// Create new user with API role
|
|
||||||
User user = new User();
|
|
||||||
user.setUsername(username);
|
|
||||||
user.setPassword(UUID.randomUUID().toString());
|
|
||||||
user.setEnabled(true);
|
|
||||||
user.setFirstLogin(false);
|
|
||||||
user.setAuthenticationType(AuthenticationType.WEB);
|
|
||||||
user.setApiKey(customApiKey);
|
|
||||||
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
|
||||||
userRepository.save(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
databaseService.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
} catch (SQLException | UnsupportedProviderException e) {
|
} else {
|
||||||
log.error("Error exporting database after synchronising custom API user", e);
|
// Update API key if it has changed
|
||||||
|
User user = existingUser.get();
|
||||||
|
if (!customApiKey.equals(user.getApiKey())) {
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseService.exportDatabase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security.database;
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -9,10 +11,9 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
@@ -32,11 +33,7 @@ public class DatabaseConfig {
|
|||||||
public DatabaseConfig(
|
public DatabaseConfig(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@Qualifier("runningEE") boolean runningEE) {
|
@Qualifier("runningEE") boolean runningEE) {
|
||||||
DATASOURCE_DEFAULT_URL =
|
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";
|
||||||
"jdbc:h2:file:"
|
|
||||||
+ InstallationPathConfig.getConfigPath()
|
|
||||||
+ "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
|
||||||
log.debug("Database URL: {}", DATASOURCE_DEFAULT_URL);
|
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.runningEE = runningEE;
|
this.runningEE = runningEE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.springframework.jdbc.datasource.init.ScriptException;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user