mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-04-19 11:11:18 +00:00
hashs
This commit is contained in:
parent
6906344178
commit
13c7a1fb16
81
.github/workflows/file_hash_generation.yml
vendored
Normal file
81
.github/workflows/file_hash_generation.yml
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
name: Generate Template Hashes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- hashs
|
||||
paths:
|
||||
- 'src/main/resources/templates/**'
|
||||
- 'src/main/resources/static/**'
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
generate-hash:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Calculate template hashes
|
||||
id: hash
|
||||
run: |
|
||||
# Create a script to calculate individual file hashes
|
||||
cat > calculate-hashes.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Directories to hash
|
||||
TEMPLATE_DIR="src/main/resources/templates"
|
||||
STATIC_DIR="src/main/resources/static"
|
||||
OUTPUT_FILE="src/main/resources/reference-hash.json"
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p $(dirname "$OUTPUT_FILE")
|
||||
|
||||
# Start JSON
|
||||
echo "{" > "$OUTPUT_FILE"
|
||||
|
||||
# Find all files and calculate CRC32 hash
|
||||
FIRST=true
|
||||
find "$TEMPLATE_DIR" "$STATIC_DIR" -type f | sort | while read file; do
|
||||
# Get relative path from src/main/resources
|
||||
REL_PATH=$(echo "$file" | sed 's|^src/main/resources/||')
|
||||
|
||||
# Calculate CRC32 hash (faster than MD5)
|
||||
HASH=$(crc32 "$file" 2>/dev/null || cksum "$file" | awk '{print $1}')
|
||||
|
||||
# Add to JSON
|
||||
if [ "$FIRST" = true ]; then
|
||||
FIRST=false
|
||||
else
|
||||
echo "," >> "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
echo " \"$REL_PATH\": \"$HASH\"" >> "$OUTPUT_FILE"
|
||||
done
|
||||
|
||||
# End JSON
|
||||
echo "}" >> "$OUTPUT_FILE"
|
||||
|
||||
echo "Generated hashes for $(grep -c ":" "$OUTPUT_FILE") files"
|
||||
EOF
|
||||
|
||||
chmod +x calculate-hashes.sh
|
||||
./calculate-hashes.sh
|
||||
|
||||
- name: Commit and push if changed
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
|
||||
git add src/main/resources/reference-hash.json
|
||||
|
||||
# Only commit if there are changes
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "Update template reference hashes [skip ci]"
|
||||
git push
|
||||
fi
|
@ -0,0 +1,172 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
@Configuration
|
||||
public class TemplateIntegrityConfig {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TemplateIntegrityConfig.class);
|
||||
|
||||
// Buffer size for reading files (8KB is a good balance)
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
@Value("${template.hash.reference:classpath:reference-hash.json}")
|
||||
private String referenceHashPath;
|
||||
|
||||
@Value("${template.directories:classpath:templates/,classpath:static/}")
|
||||
private String[] templateDirectories;
|
||||
|
||||
public TemplateIntegrityConfig(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public boolean templatesModified() {
|
||||
try {
|
||||
Map<String, String> referenceHashes = loadReferenceHashes();
|
||||
|
||||
// Check for modifications with early termination
|
||||
if (checkForModifications(referenceHashes)) {
|
||||
logger.info("SECURITY WARNING: Templates appear to have been modified from the release version!");
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.info("Template integrity verified successfully");
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error verifying template integrity", e);
|
||||
// In case of error, assume modified for security
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> loadReferenceHashes() throws IOException {
|
||||
Resource resource = resourceLoader.getResource(referenceHashPath);
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
String content = new String(is.readAllBytes());
|
||||
return parseHashJson(content);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> parseHashJson(String json) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
// Simple JSON parsing to avoid additional dependencies
|
||||
String[] entries = json.replaceAll("[{}\"]", "").split(",");
|
||||
for (String entry : entries) {
|
||||
String[] parts = entry.trim().split(":");
|
||||
if (parts.length == 2) {
|
||||
result.put(parts[0].trim(), parts[1].trim());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean checkForModifications(Map<String, String> referenceHashes) throws IOException {
|
||||
// Track files we've found to check for missing files later
|
||||
Map<String, Boolean> foundFiles = new HashMap<>();
|
||||
for (String key : referenceHashes.keySet()) {
|
||||
foundFiles.put(key, false);
|
||||
}
|
||||
|
||||
AtomicBoolean modified = new AtomicBoolean(false);
|
||||
|
||||
// Check each directory
|
||||
for (String dir : templateDirectories) {
|
||||
if (modified.get()) {
|
||||
break; // Early termination
|
||||
}
|
||||
|
||||
// Remove classpath: prefix if present
|
||||
String dirPath = dir.replace("classpath:", "");
|
||||
|
||||
// Get the resource as a file
|
||||
Resource resource = resourceLoader.getResource("classpath:" + dirPath);
|
||||
try {
|
||||
Path directory = Paths.get(resource.getURI());
|
||||
|
||||
if (Files.exists(directory) && Files.isDirectory(directory)) {
|
||||
// Walk the directory tree
|
||||
Files.walk(directory)
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(path -> {
|
||||
if (modified.get()) return; // Skip if already found modification
|
||||
|
||||
try {
|
||||
String relativePath = directory.relativize(path).toString();
|
||||
// Track that we found this file
|
||||
foundFiles.put(relativePath, true);
|
||||
|
||||
// Check if this file is in our reference
|
||||
String referenceHash = referenceHashes.get(relativePath);
|
||||
if (referenceHash == null) {
|
||||
// New file found
|
||||
logger.info("New file detected: {}", relativePath);
|
||||
modified.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the hash matches
|
||||
String currentHash = computeFileHash(path);
|
||||
if (!currentHash.equals(referenceHash)) {
|
||||
logger.info("Modified file detected: {}", relativePath);
|
||||
modified.set(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("Failed to hash file: {}", path, e);
|
||||
modified.set(true); // Fail safe
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error accessing directory: {}", dirPath, e);
|
||||
return true; // Assume modified on error
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't found a modification yet, check for missing files
|
||||
if (!modified.get()) {
|
||||
for (Map.Entry<String, Boolean> entry : foundFiles.entrySet()) {
|
||||
if (!entry.getValue()) {
|
||||
// File was in reference but not found
|
||||
logger.info("Missing file detected: {}", entry.getKey());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified.get();
|
||||
}
|
||||
|
||||
private String computeFileHash(Path filePath) throws IOException {
|
||||
Checksum checksum = new CRC32(); // Much faster than MD5 or SHA
|
||||
|
||||
try (InputStream is = Files.newInputStream(filePath)) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = is.read(buffer)) != -1) {
|
||||
checksum.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return Long.toHexString(checksum.getValue());
|
||||
}
|
||||
}
|
0
src/main/resources/reference-hash.json
Normal file
0
src/main/resources/reference-hash.json
Normal file
@ -6,12 +6,10 @@
|
||||
# ___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _| #
|
||||
# |____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_| #
|
||||
# #
|
||||
# Custom setting.yml file with all endpoints disabled to only be used for testing purposes #
|
||||
# Do not comment out any entry, it will be removed on next startup #
|
||||
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME #
|
||||
#############################################################################################################
|
||||
|
||||
|
||||
security:
|
||||
enableLogin: false # set to 'true' to enable login
|
||||
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
||||
@ -62,15 +60,21 @@ security:
|
||||
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
|
||||
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
|
||||
|
||||
enterpriseEdition:
|
||||
enabled: false # set to 'true' to enable enterprise edition
|
||||
premium:
|
||||
key: 00000000-0000-0000-0000-000000000000
|
||||
SSOAutoLogin: false # Enable to auto login to first provided SSO
|
||||
CustomMetadata:
|
||||
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
||||
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
|
||||
creator: Stirling-PDF # supports text such as 'Company-PDF'
|
||||
producer: Stirling-PDF # supports text such as 'Company-PDF'
|
||||
enabled: false # Enable license key checks for pro/enterprise features
|
||||
proFeatures:
|
||||
SSOAutoLogin: false
|
||||
CustomMetadata:
|
||||
autoUpdateMetadata: false
|
||||
author: username
|
||||
creator: Stirling-PDF
|
||||
producer: Stirling-PDF
|
||||
googleDrive:
|
||||
enabled: false
|
||||
clientId: ''
|
||||
apiKey: ''
|
||||
appId: ''
|
||||
|
||||
legal:
|
||||
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
||||
@ -88,6 +92,7 @@ system:
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
||||
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
||||
enableAnalytics: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
||||
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
|
||||
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
||||
datasource:
|
||||
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
|
||||
@ -100,13 +105,12 @@ system:
|
||||
name: postgres # set the name of your database. Should match the name of the database you create
|
||||
customPaths:
|
||||
pipeline:
|
||||
watchedFoldersDir: "" #Defaults to /pipeline/watchedFolders
|
||||
finishedFoldersDir: "" #Defaults to /pipeline/finishedFolders
|
||||
watchedFoldersDir: '' #Defaults to /pipeline/watchedFolders
|
||||
finishedFoldersDir: '' #Defaults to /pipeline/finishedFolders
|
||||
operations:
|
||||
weasyprint: "" #Defaults to /opt/venv/bin/weasyprint
|
||||
unoconvert: "" #Defaults to /opt/venv/bin/unoconvert
|
||||
|
||||
|
||||
weasyprint: '' #Defaults to /opt/venv/bin/weasyprint
|
||||
unoconvert: '' #Defaults to /opt/venv/bin/unoconvert
|
||||
fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
|
||||
|
||||
ui:
|
||||
appName: '' # application's visible name
|
||||
@ -114,7 +118,7 @@ ui:
|
||||
appNameNavbar: '' # name displayed on the navigation bar
|
||||
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
|
||||
|
||||
endpoints: # All the possible endpoints are disabled
|
||||
endpoints:
|
||||
toRemove: [crop, merge-pdfs, multi-page-layout, overlay-pdfs, pdf-to-single-page, rearrange-pages, remove-image-pdf, remove-pages, rotate-pdf, scale-pages, split-by-size-or-count, split-pages, split-pdf-by-chapters, split-pdf-by-sections, add-password, add-watermark, auto-redact, cert-sign, get-info-on-pdf, redact, remove-cert-sign, remove-password, sanitize-pdf, validate-signature, file-to-pdf, html-to-pdf, img-to-pdf, markdown-to-pdf, pdf-to-csv, pdf-to-html, pdf-to-img, pdf-to-markdown, pdf-to-pdfa, pdf-to-presentation, pdf-to-text, pdf-to-word, pdf-to-xml, url-to-pdf, add-image, add-page-numbers, add-stamp, auto-rename, auto-split-pdf, compress-pdf, decompress-pdf, extract-image-scans, extract-images, flatten, ocr-pdf, remove-blanks, repair, replace-invert-pdf, show-javascript, update-metadata, filter-contains-image, filter-contains-text, filter-file-size, filter-page-count, filter-page-rotation, filter-page-size] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
|
||||
|
||||
@ -125,7 +129,7 @@ metrics:
|
||||
AutomaticallyGenerated:
|
||||
key: cbb81c0f-50b1-450c-a2b5-89ae527776eb
|
||||
UUID: 10dd4fba-01fa-4717-9b78-3dc4f54e398a
|
||||
appVersion: 0.44.3
|
||||
appVersion: 0.45.6
|
||||
|
||||
processExecutor:
|
||||
sessionLimit: # Process executor instances limits
|
||||
|
@ -51,3 +51,4 @@
|
||||
/swagger-ui/index.html
|
||||
/licenses
|
||||
/releases
|
||||
/v1/api-docs
|
@ -62,4 +62,5 @@
|
||||
/stamp
|
||||
/validate-signature
|
||||
/view-pdf
|
||||
/swagger-ui/index.html
|
||||
/swagger-ui/index.html
|
||||
/v1/api-docs
|
Loading…
x
Reference in New Issue
Block a user