Compare commits
85 Commits
v0.36.1
...
formatting
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6119181276 | ||
|
|
bf9a00868d | ||
|
|
a58b4d2286 | ||
|
|
a71cb7f5d9 | ||
|
|
a9e250547a | ||
|
|
30bcdbd7be | ||
|
|
61603aebb2 | ||
|
|
8dca4a588d | ||
|
|
63386baa0d | ||
|
|
2a93910da3 | ||
|
|
fca6dc1fd8 | ||
|
|
2d82c5fa77 | ||
|
|
167c792bf0 | ||
|
|
95a9e10dc8 | ||
|
|
4d6368048c | ||
|
|
4e715a82e0 | ||
|
|
164381e940 | ||
|
|
0436f45de5 | ||
|
|
6ce761aff2 | ||
|
|
8a5d9f9a95 | ||
|
|
c0ef624a1d | ||
|
|
b9ae90274f | ||
|
|
764b8f4d22 | ||
|
|
a531f53893 | ||
|
|
74d6d96f4e | ||
|
|
1862ab1671 | ||
|
|
bf95ca43dc | ||
|
|
678ef85da1 | ||
|
|
9a6afdd921 | ||
|
|
a72615cc86 | ||
|
|
b0daac2566 | ||
|
|
2335ecf017 | ||
|
|
a79318bf0c | ||
|
|
9eed761346 | ||
|
|
12d86049f6 | ||
|
|
e9f80d03ea | ||
|
|
42c238d87d | ||
|
|
051cd2d0d5 | ||
|
|
eb1301464d | ||
|
|
720705e422 | ||
|
|
db3a8a8483 | ||
|
|
2bdda9acba | ||
|
|
8053035158 | ||
|
|
bd20d3ad93 | ||
|
|
c6700b34b0 | ||
|
|
25b66a0775 | ||
|
|
ac9f4a4c7e | ||
|
|
3a0d89bd7c | ||
|
|
5d4769df56 | ||
|
|
80b9d2691a | ||
|
|
99c598096e | ||
|
|
b3dfdba9cb | ||
|
|
c2e96c5f1d | ||
|
|
cffdbc5e7b | ||
|
|
e8d7b4aded | ||
|
|
d672ad22bc | ||
|
|
2c0a9ae082 | ||
|
|
bbedf22678 | ||
|
|
e9431ba6b5 | ||
|
|
d74c521bc0 | ||
|
|
fa57ee4ef9 | ||
|
|
be3cbcc7cd | ||
|
|
317d985166 | ||
|
|
64f8348215 | ||
|
|
7f045dbcc7 | ||
|
|
343e38a5fd | ||
|
|
af100d4190 | ||
|
|
4998ad064a | ||
|
|
64ee26facf | ||
|
|
e273b9a3ad | ||
|
|
42541a7174 | ||
|
|
ca3002f925 | ||
|
|
6c172af3c1 | ||
|
|
60ced19f64 | ||
|
|
88079ddf7f | ||
|
|
9b6dcdcd06 | ||
|
|
63eb94c0a6 | ||
|
|
0a6b6453b2 | ||
|
|
2931e348a9 | ||
|
|
c71ca21532 | ||
|
|
0e4c3d5dbc | ||
|
|
92cabf125e | ||
|
|
818bed3154 | ||
|
|
faf3454a02 | ||
|
|
1f1c414138 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -76,7 +76,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.7"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Pip requirements
|
- name: Pip requirements
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
13
.github/workflows/multiOSReleases.yml
vendored
13
.github/workflows/multiOSReleases.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}"
|
||||||
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
||||||
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
else
|
else
|
||||||
@@ -85,7 +85,12 @@ jobs:
|
|||||||
- name: Upload Installer Artifact
|
- name: Upload Installer Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Stirling-PDF-${{ matrix.platform }}-installer.{{ matrix.ext }}
|
name: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
||||||
path: Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}
|
path: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
|
||||||
|
|||||||
48
.github/workflows/releaseArtifacts.yml
vendored
48
.github/workflows/releaseArtifacts.yml
vendored
@@ -59,13 +59,13 @@ jobs:
|
|||||||
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
- name: Rename jar binaries
|
- name: Rename jar binaries
|
||||||
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload Assets jar binaries
|
- name: Upload Assets jar binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
name: Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -73,44 +73,4 @@ jobs:
|
|||||||
- name: Upload jar binaries to release
|
- name: Upload jar binaries to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
|
||||||
push-ui:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: "17"
|
|
||||||
distribution: "temurin"
|
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@v4
|
|
||||||
with:
|
|
||||||
gradle-version: 8.7
|
|
||||||
|
|
||||||
- name: Generate exe
|
|
||||||
run: ./gradlew clean createExe
|
|
||||||
env:
|
|
||||||
DOCKER_ENABLE_SECURITY: false
|
|
||||||
STIRLING_PDF_DESKTOP_UI: true
|
|
||||||
|
|
||||||
- name: Get version number
|
|
||||||
id: versionNumber
|
|
||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload Assets binarie
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ./build/launch4j/Stirling-PDF.exe
|
|
||||||
name: Stirling-PDF.exe
|
|
||||||
overwrite: true
|
|
||||||
retention-days: 1
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload binaries to release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: ./build/launch4j/Stirling-PDF.exe
|
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -49,5 +49,7 @@
|
|||||||
"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,
|
||||||
|
"java.format.settings.google.mode": "jar-file",
|
||||||
|
"java.format.settings.google.extra": "--aosp --skip-sorting-imports"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
# New Database Backup and Import Functionality
|
# New Database Backup and Import Functionality
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> **Full activation will take place on approximately January 5th, 2025!**
|
|
||||||
|
|
||||||
Why is the waiting time six months?
|
|
||||||
|
|
||||||
There are users who only install updates sporadically; if they skip the preparation, it can/will lead to data loss in the database.
|
|
||||||
|
|
||||||
## Functionality Overview
|
## Functionality Overview
|
||||||
|
|
||||||
The newly introduced feature enhances the application with robust database backup and import capabilities. This feature is designed to ensure data integrity and provide a straightforward way to manage database backups. Here's how it works:
|
The newly introduced feature enhances the application with robust database backup and import capabilities. This feature is designed to ensure data integrity and provide a straightforward way to manage database backups. Here's how it works:
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -192,32 +192,32 @@ 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) |  |
|
||||||
|
|||||||
60
build.gradle
60
build.gradle
@@ -22,12 +22,12 @@ ext {
|
|||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.36"
|
lombokVersion = "1.18.36"
|
||||||
bouncycastleVersion = "1.79"
|
bouncycastleVersion = "1.79"
|
||||||
springSecuritySamlVersion = "6.4.1"
|
springSecuritySamlVersion = "6.4.2"
|
||||||
openSamlVersion = "4.3.2"
|
openSamlVersion = "4.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.36.1"
|
version = "0.36.5"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -38,13 +38,9 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
|
|
||||||
maven {
|
|
||||||
url 'https://build.shibboleth.net/maven/releases'
|
|
||||||
}
|
|
||||||
maven { url "https://build.shibboleth.net/maven/releases" }
|
maven { url "https://build.shibboleth.net/maven/releases" }
|
||||||
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@@ -68,12 +64,12 @@ sourceSets {
|
|||||||
exclude "stirling/software/SPDF/model/User.java"
|
exclude "stirling/software/SPDF/model/User.java"
|
||||||
exclude "stirling/software/SPDF/repository/**"
|
exclude "stirling/software/SPDF/repository/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
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/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +100,7 @@ jpackage {
|
|||||||
|
|
||||||
icon = "src/main/resources/static/favicon.ico"
|
icon = "src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// JVM Options
|
// JVM Options
|
||||||
javaOptions = [
|
javaOptions = [
|
||||||
@@ -115,12 +111,12 @@ jpackage {
|
|||||||
"--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"
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
verbose = true
|
verbose = true
|
||||||
|
|
||||||
destination = "${projectDir}/build/jpackage"
|
destination = "${projectDir}/build/jpackage"
|
||||||
|
|
||||||
// Windows-specific configuration
|
// Windows-specific configuration
|
||||||
@@ -150,16 +146,16 @@ jpackage {
|
|||||||
macAppCategory = "public.app-category.productivity"
|
macAppCategory = "public.app-category.productivity"
|
||||||
macSign = false // Enable signing
|
macSign = false // Enable signing
|
||||||
macAppStore = false // Not targeting App Store initially
|
macAppStore = false // Not targeting App Store initially
|
||||||
|
|
||||||
//installDir = "Applications"
|
//installDir = "Applications"
|
||||||
|
|
||||||
// Add license and other documentation to DMG
|
// Add license and other documentation to DMG
|
||||||
/*macDmgContent = [
|
/*macDmgContent = [
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"CHANGELOG.md"
|
"CHANGELOG.md"
|
||||||
]*/
|
]*/
|
||||||
|
|
||||||
// Enable Mac-specific entitlements
|
// Enable Mac-specific entitlements
|
||||||
//macEntitlements = "entitlements.plist" // You'll need to create this file
|
//macEntitlements = "entitlements.plist" // You'll need to create this file
|
||||||
}
|
}
|
||||||
@@ -169,7 +165,7 @@ jpackage {
|
|||||||
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
|
||||||
|
|
||||||
// Debian package configuration
|
// Debian package configuration
|
||||||
//linuxPackageName = "stirlingpdf"
|
//linuxPackageName = "stirlingpdf"
|
||||||
linuxDebMaintainer = "support@stirlingpdf.com"
|
linuxDebMaintainer = "support@stirlingpdf.com"
|
||||||
@@ -177,9 +173,9 @@ jpackage {
|
|||||||
linuxAppCategory = "Office"
|
linuxAppCategory = "Office"
|
||||||
linuxAppRelease = "1"
|
linuxAppRelease = "1"
|
||||||
linuxPackageDeps = true
|
linuxPackageDeps = true
|
||||||
|
|
||||||
installDir = "/opt/Stirling-PDF"
|
installDir = "/opt/Stirling-PDF"
|
||||||
|
|
||||||
// RPM-specific settings
|
// RPM-specific settings
|
||||||
//linuxRpmLicenseType = "MIT"
|
//linuxRpmLicenseType = "MIT"
|
||||||
}
|
}
|
||||||
@@ -212,7 +208,7 @@ launch4j {
|
|||||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
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 {
|
||||||
@@ -222,15 +218,15 @@ launch4j {
|
|||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 21?"
|
errTitle="Encountered error, Do you have Java 21?"
|
||||||
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"
|
||||||
@@ -247,7 +243,7 @@ spotless {
|
|||||||
java {
|
java {
|
||||||
target project.fileTree('src/main/java')
|
target project.fileTree('src/main/java')
|
||||||
|
|
||||||
googleJavaFormat("1.22.0").aosp().reorderImports(false)
|
googleJavaFormat("1.25.2").aosp().reorderImports(false)
|
||||||
|
|
||||||
importOrder("java", "javax", "org", "com", "net", "io")
|
importOrder("java", "javax", "org", "com", "net", "io")
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
@@ -276,11 +272,11 @@ dependencies {
|
|||||||
implementation "org.openjfx:javafx-controls:21"
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
implementation "org.openjfx:javafx-swing:21"
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
}
|
}
|
||||||
|
|
||||||
//security updates
|
|
||||||
implementation "org.springframework:spring-webmvc:6.2.0"
|
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
//security updates
|
||||||
|
implementation "org.springframework:spring-webmvc:6.2.1"
|
||||||
|
|
||||||
|
implementation("io.github.pixee:java-security-toolkit:1.2.1")
|
||||||
|
|
||||||
// implementation "org.yaml:snakeyaml:2.2"
|
// implementation "org.yaml:snakeyaml:2.2"
|
||||||
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
|
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
|
||||||
@@ -301,7 +297,7 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||||
|
|
||||||
implementation '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"
|
||||||
@@ -372,7 +368,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.1"
|
implementation "io.micrometer:micrometer-core:1.14.2"
|
||||||
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"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ fi
|
|||||||
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
||||||
if [[ -n "$TESSERACT_LANGS" ]]; then
|
if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||||
# Convert comma-separated values to a space-separated list
|
# Convert comma-separated values to a space-separated list
|
||||||
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
SPACE_SEPARATED_LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
||||||
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
||||||
# Install each language pack
|
# Install each language pack
|
||||||
for LANG in $LANGS; do
|
for LANG in $SPACE_SEPARATED_LANGS; do
|
||||||
if [[ $LANG =~ $pattern ]]; then
|
if [[ $LANG =~ $pattern ]]; then
|
||||||
apk add --no-cache "tesseract-ocr-data-$LANG"
|
apk add --no-cache "tesseract-ocr-data-$LANG"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -97,14 +97,14 @@ public abstract class CreateSignatureBase implements SignatureInterface {
|
|||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setCertificateChain(final Certificate[] certificateChain) {
|
|
||||||
this.certificateChain = certificateChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Certificate[] getCertificateChain() {
|
public Certificate[] getCertificateChain() {
|
||||||
return certificateChain;
|
return certificateChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void setCertificateChain(final Certificate[] certificateChain) {
|
||||||
|
this.certificateChain = certificateChain;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTsaUrl(String tsaUrl) {
|
public void setTsaUrl(String tsaUrl) {
|
||||||
this.tsaUrl = tsaUrl;
|
this.tsaUrl = tsaUrl;
|
||||||
}
|
}
|
||||||
@@ -152,6 +152,10 @@ public abstract class CreateSignatureBase implements SignatureInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isExternalSigning() {
|
||||||
|
return externalSigning;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set if external signing scenario should be used. If {@code false}, SignatureInterface would
|
* Set if external signing scenario should be used. If {@code false}, SignatureInterface would
|
||||||
* be used for signing.
|
* be used for signing.
|
||||||
@@ -163,8 +167,4 @@ public abstract class CreateSignatureBase implements SignatureInterface {
|
|||||||
public void setExternalSigning(boolean externalSigning) {
|
public void setExternalSigning(boolean externalSigning) {
|
||||||
this.externalSigning = externalSigning;
|
this.externalSigning = externalSigning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExternalSigning() {
|
|
||||||
return externalSigning;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,15 +51,13 @@ public class TSAClient {
|
|||||||
|
|
||||||
private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =
|
private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =
|
||||||
new DefaultDigestAlgorithmIdentifierFinder();
|
new DefaultDigestAlgorithmIdentifierFinder();
|
||||||
|
// SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
|
||||||
|
private static final Random RANDOM = new SecureRandom();
|
||||||
private final URL url;
|
private final URL url;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String password;
|
private final String password;
|
||||||
private final MessageDigest digest;
|
private final MessageDigest digest;
|
||||||
|
|
||||||
// SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
|
|
||||||
private static final Random RANDOM = new SecureRandom();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param url the URL of the TSA service
|
* @param url the URL of the TSA service
|
||||||
* @param username user name of TSA
|
* @param username user name of TSA
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.EE;
|
package stirling.software.SPDF.EE;
|
||||||
|
|
||||||
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.Configuration;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
@@ -14,8 +13,15 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class EEAppConfig {
|
public class EEAppConfig {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
|
||||||
|
private final LicenseKeyChecker licenseKeyChecker;
|
||||||
|
|
||||||
|
public EEAppConfig(
|
||||||
|
ApplicationProperties applicationProperties, LicenseKeyChecker licenseKeyChecker) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.licenseKeyChecker = licenseKeyChecker;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "runningEE")
|
@Bean(name = "runningEE")
|
||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import java.net.Socket;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import io.github.pixee.security.SystemCommand;
|
import io.github.pixee.security.SystemCommand;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -16,23 +13,20 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class LibreOfficeListener {
|
public class LibreOfficeListener {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
|
||||||
private static final long ACTIVITY_TIMEOUT = 20L * 60 * 1000; // 20 minutes
|
private static final long ACTIVITY_TIMEOUT = 20L * 60 * 1000; // 20 minutes
|
||||||
|
|
||||||
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
|
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
|
||||||
private static final int LISTENER_PORT = 2002;
|
private static final int LISTENER_PORT = 2002;
|
||||||
|
private ExecutorService executorService;
|
||||||
|
private long lastActivityTime;
|
||||||
|
private Process process;
|
||||||
|
|
||||||
|
private LibreOfficeListener() {}
|
||||||
|
|
||||||
public static LibreOfficeListener getInstance() {
|
public static LibreOfficeListener getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService executorService;
|
|
||||||
private long lastActivityTime;
|
|
||||||
|
|
||||||
private Process process;
|
|
||||||
|
|
||||||
private LibreOfficeListener() {}
|
|
||||||
|
|
||||||
private boolean isListenerRunning() {
|
private boolean isListenerRunning() {
|
||||||
log.info("waiting for listener to start");
|
log.info("waiting for listener to start");
|
||||||
try (Socket socket = new Socket()) {
|
try (Socket socket = new Socket()) {
|
||||||
@@ -87,7 +81,7 @@ public class LibreOfficeListener {
|
|||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
} // Check every 1 second
|
} // Check every 1 second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -11,10 +10,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
@@ -23,6 +18,7 @@ import org.springframework.core.env.Environment;
|
|||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import io.github.pixee.security.SystemCommand;
|
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;
|
||||||
@@ -35,26 +31,22 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
|
||||||
|
|
||||||
@Autowired private Environment env;
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
private static String baseUrlStatic;
|
private static String baseUrlStatic;
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
private final Environment env;
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final WebBrowser webBrowser;
|
||||||
|
|
||||||
@Value("${baseUrl:http://localhost}")
|
@Value("${baseUrl:http://localhost}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
public SPdfApplication(
|
||||||
public void setServerPortStatic(String port) {
|
Environment env,
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
ApplicationProperties applicationProperties,
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
@Autowired(required = false) WebBrowser webBrowser) {
|
||||||
SPdfApplication.serverPortStatic =
|
this.env = env;
|
||||||
"0"; // This will let Spring Boot assign an available port
|
this.applicationProperties = applicationProperties;
|
||||||
} else {
|
this.webBrowser = webBrowser;
|
||||||
SPdfApplication.serverPortStatic = port;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||||
@@ -75,29 +67,23 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
|
||||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||||
|
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
|
|
||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
System.setProperty("java.awt.headless", "false");
|
System.setProperty("java.awt.headless", "false");
|
||||||
app.setHeadless(false);
|
app.setHeadless(false);
|
||||||
props.put("java.awt.headless", "false");
|
props.put("java.awt.headless", "false");
|
||||||
props.put("spring.main.web-application-type", "servlet");
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setAdditionalProfiles("default");
|
app.setAdditionalProfiles("default");
|
||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
// External config files
|
// External config files
|
||||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||||
} else {
|
} else {
|
||||||
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
|
log.warn("External configuration file 'configs/settings.yml' does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
@@ -108,43 +94,54 @@ public class SPdfApplication {
|
|||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
existingLocation + "file:configs/custom_settings.yml");
|
existingLocation + "file:configs/custom_settings.yml");
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
log.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||||
}
|
}
|
||||||
Properties finalProps = new Properties();
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
if (!propertyFiles.isEmpty()) {
|
if (!propertyFiles.isEmpty()) {
|
||||||
finalProps.putAll(
|
finalProps.putAll(
|
||||||
Collections.singletonMap(
|
Collections.singletonMap(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
propertyFiles.get("spring.config.additional-location")));
|
propertyFiles.get("spring.config.additional-location")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.isEmpty()) {
|
if (!props.isEmpty()) {
|
||||||
finalProps.putAll(props);
|
finalProps.putAll(props);
|
||||||
}
|
}
|
||||||
app.setDefaultProperties(finalProps);
|
app.setDefaultProperties(finalProps);
|
||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
// Ensure directories are created
|
// Ensure directories are created
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of("customFiles/static/"));
|
Files.createDirectories(Path.of("customFiles/static/"));
|
||||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
Files.createDirectories(Path.of("customFiles/templates/"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error creating directories: {}", e.getMessage());
|
log.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
printStartupLogs();
|
printStartupLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printStartupLogs() {
|
private static void printStartupLogs() {
|
||||||
logger.info("Stirling-PDF Started.");
|
log.info("Stirling-PDF Started.");
|
||||||
String url = baseUrlStatic + ":" + getStaticPort();
|
String url = baseUrlStatic + ":" + getStaticPort();
|
||||||
logger.info("Navigate to {}", url);
|
log.info("Navigate to {}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired(required = false)
|
public static String getStaticBaseUrl() {
|
||||||
private WebBrowser webBrowser;
|
return baseUrlStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStaticPort() {
|
||||||
|
return serverPortStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${server.port:8080}")
|
||||||
|
public void setServerPortStatic(String port) {
|
||||||
|
if ("auto".equalsIgnoreCase(port)) {
|
||||||
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
|
SPdfApplication.serverPortStatic = // This will let Spring Boot assign an available port
|
||||||
|
"0";
|
||||||
|
} else {
|
||||||
|
SPdfApplication.serverPortStatic = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -154,7 +151,7 @@ public class SPdfApplication {
|
|||||||
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
webBrowser.initWebUI(url);
|
webBrowser.initWebUI(url);
|
||||||
} else {
|
} else {
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
@@ -169,11 +166,11 @@ public class SPdfApplication {
|
|||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
log.error("Error opening browser: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info("Running configs {}", applicationProperties.toString());
|
log.info("Running configs {}", applicationProperties.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -183,18 +180,10 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNonStaticBaseUrl() {
|
public String getNonStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getStaticPort() {
|
|
||||||
return serverPortStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNonStaticPort() {
|
public String getNonStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -21,15 +18,19 @@ import org.springframework.core.io.Resource;
|
|||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@Slf4j
|
||||||
public class AppConfig {
|
public class AppConfig {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AppConfig.class);
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
public AppConfig(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
@@ -61,7 +62,7 @@ public class AppConfig {
|
|||||||
props.load(resource.getInputStream());
|
props.load(resource.getInputStream());
|
||||||
return props.getProperty("version");
|
return props.getProperty("version");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
return "0.0.0";
|
return "0.0.0";
|
||||||
}
|
}
|
||||||
@@ -101,6 +102,25 @@ public class AppConfig {
|
|||||||
return Files.exists(Paths.get("/.dockerenv"));
|
return Files.exists(Paths.get("/.dockerenv"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "configDirMounted")
|
||||||
|
public boolean isRunningInDockerWithConfig() {
|
||||||
|
Path dockerEnv = Paths.get("/.dockerenv");
|
||||||
|
// default to true if not docker
|
||||||
|
if (!Files.exists(dockerEnv)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Path mountInfo = Paths.get("/proc/1/mountinfo");
|
||||||
|
// this should always exist, if not some unknown usecase
|
||||||
|
if (!Files.exists(mountInfo)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Files.lines(mountInfo).anyMatch(line -> line.contains(" /configs "));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "bookAndHtmlFormatsInstalled")
|
@Bean(name = "bookAndHtmlFormatsInstalled")
|
||||||
public boolean bookAndHtmlFormatsInstalled() {
|
public boolean bookAndHtmlFormatsInstalled() {
|
||||||
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
|
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
|
||||||
|
|||||||
@@ -11,10 +11,16 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Service
|
@Service
|
||||||
class AppUpdateService {
|
class AppUpdateService {
|
||||||
|
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired(required = false)
|
private final ShowAdminInterface showAdmin;
|
||||||
ShowAdminInterface showAdmin;
|
|
||||||
|
public AppUpdateService(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
@Autowired(required = false) ShowAdminInterface showAdmin) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.showAdmin = showAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "shouldShow")
|
@Bean(name = "shouldShow")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
|
|||||||
@@ -16,16 +16,15 @@ import org.simpleyaml.configuration.comments.CommentType;
|
|||||||
import org.simpleyaml.configuration.file.YamlFile;
|
import org.simpleyaml.configuration.file.YamlFile;
|
||||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ConfigInitializer
|
public class ConfigInitializer
|
||||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConfigInitializer.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||||
try {
|
try {
|
||||||
@@ -149,7 +148,7 @@ public class ConfigInitializer
|
|||||||
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||||
} else {
|
} else {
|
||||||
// Log if the key is not found in both YAML files
|
// Log if the key is not found in both YAML files
|
||||||
logger.info("Key not found in both YAML files: " + path);
|
log.info("Key not found in both YAML files: " + path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,23 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
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;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
@DependsOn({"bookAndHtmlFormatsInstalled"})
|
@DependsOn({"bookAndHtmlFormatsInstalled"})
|
||||||
public class EndpointConfiguration {
|
public class EndpointConfiguration {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
|
||||||
|
private static final String REMOVE_BLANKS = "remove-blanks";
|
||||||
|
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 final ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
private boolean bookAndHtmlFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -43,7 +42,7 @@ public class EndpointConfiguration {
|
|||||||
|
|
||||||
public void disableEndpoint(String endpoint) {
|
public void disableEndpoint(String endpoint) {
|
||||||
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
||||||
logger.debug("Disabling {}", endpoint);
|
log.debug("Disabling {}", endpoint);
|
||||||
endpointStatuses.put(endpoint, false);
|
endpointStatuses.put(endpoint, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +86,7 @@ public class EndpointConfiguration {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (!disabledList.isEmpty()) {
|
if (!disabledList.isEmpty()) {
|
||||||
logger.info(
|
log.info(
|
||||||
"Total disabled endpoints: {}. Disabled endpoints: {}",
|
"Total disabled endpoints: {}. Disabled endpoints: {}",
|
||||||
disabledList.size(),
|
disabledList.size(),
|
||||||
String.join(", ", disabledList));
|
String.join(", ", disabledList));
|
||||||
@@ -287,6 +286,4 @@ public class EndpointConfiguration {
|
|||||||
public Set<String> getEndpointsForGroup(String group) {
|
public Set<String> getEndpointsForGroup(String group) {
|
||||||
return endpointGroups.getOrDefault(group, new HashSet<>());
|
return endpointGroups.getOrDefault(group, new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String REMOVE_BLANKS = "remove-blanks";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
@@ -10,7 +9,11 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
@Component
|
@Component
|
||||||
public class EndpointInterceptor implements HandlerInterceptor {
|
public class EndpointInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
@Autowired private EndpointConfiguration endpointConfiguration;
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
|
||||||
|
public EndpointInterceptor(EndpointConfiguration endpointConfiguration) {
|
||||||
|
this.endpointConfiguration = endpointConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(
|
public boolean preHandle(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -15,7 +14,24 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ExternalAppDepConfig {
|
public class ExternalAppDepConfig {
|
||||||
@Autowired private EndpointConfiguration endpointConfiguration;
|
|
||||||
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
private final Map<String, List<String>> commandToGroupMapping =
|
||||||
|
new HashMap<>() {
|
||||||
|
|
||||||
|
{
|
||||||
|
put("soffice", List.of("LibreOffice"));
|
||||||
|
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(EndpointConfiguration endpointConfiguration) {
|
||||||
|
this.endpointConfiguration = endpointConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isCommandAvailable(String command) {
|
private boolean isCommandAvailable(String command) {
|
||||||
try {
|
try {
|
||||||
@@ -34,18 +50,6 @@ public class ExternalAppDepConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<String, List<String>> commandToGroupMapping =
|
|
||||||
new HashMap<>() {
|
|
||||||
{
|
|
||||||
put("soffice", List.of("LibreOffice"));
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private List<String> getAffectedFeatures(String group) {
|
private List<String> getAffectedFeatures(String group) {
|
||||||
return endpointConfiguration.getEndpointsForGroup(group).stream()
|
return endpointConfiguration.getEndpointsForGroup(group).stream()
|
||||||
.map(endpoint -> formatEndpointAsFeature(endpoint))
|
.map(endpoint -> formatEndpointAsFeature(endpoint))
|
||||||
@@ -55,7 +59,6 @@ public class ExternalAppDepConfig {
|
|||||||
private String formatEndpointAsFeature(String endpoint) {
|
private String formatEndpointAsFeature(String endpoint) {
|
||||||
// First replace common terms
|
// First replace common terms
|
||||||
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
||||||
|
|
||||||
// Split into words and capitalize each word
|
// Split into words and capitalize each word
|
||||||
return Arrays.stream(feature.split("\\s+"))
|
return Arrays.stream(feature.split("\\s+"))
|
||||||
.map(word -> capitalizeWord(word))
|
.map(word -> capitalizeWord(word))
|
||||||
@@ -76,7 +79,6 @@ public class ExternalAppDepConfig {
|
|||||||
boolean isAvailable = isCommandAvailable(command);
|
boolean isAvailable = isCommandAvailable(command);
|
||||||
if (!isAvailable) {
|
if (!isAvailable) {
|
||||||
List<String> affectedGroups = commandToGroupMapping.get(command);
|
List<String> affectedGroups = commandToGroupMapping.get(command);
|
||||||
|
|
||||||
if (affectedGroups != null) {
|
if (affectedGroups != null) {
|
||||||
for (String group : affectedGroups) {
|
for (String group : affectedGroups) {
|
||||||
List<String> affectedFeatures = getAffectedFeatures(group);
|
List<String> affectedFeatures = getAffectedFeatures(group);
|
||||||
@@ -95,7 +97,6 @@ public class ExternalAppDepConfig {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void checkDependencies() {
|
public void checkDependencies() {
|
||||||
|
|
||||||
// Check core dependencies
|
// Check core dependencies
|
||||||
checkDependencyAndDisableGroup("tesseract");
|
checkDependencyAndDisableGroup("tesseract");
|
||||||
checkDependencyAndDisableGroup("soffice");
|
checkDependencyAndDisableGroup("soffice");
|
||||||
@@ -103,13 +104,11 @@ public class ExternalAppDepConfig {
|
|||||||
checkDependencyAndDisableGroup("weasyprint");
|
checkDependencyAndDisableGroup("weasyprint");
|
||||||
checkDependencyAndDisableGroup("pdftohtml");
|
checkDependencyAndDisableGroup("pdftohtml");
|
||||||
checkDependencyAndDisableGroup("unoconv");
|
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) {
|
||||||
List<String> pythonFeatures = getAffectedFeatures("Python");
|
List<String> pythonFeatures = getAffectedFeatures("Python");
|
||||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||||
|
|
||||||
endpointConfiguration.disableGroup("Python");
|
endpointConfiguration.disableGroup("Python");
|
||||||
endpointConfiguration.disableGroup("OpenCV");
|
endpointConfiguration.disableGroup("OpenCV");
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
@@ -23,25 +22,26 @@ import stirling.software.SPDF.utils.GeneralUtils;
|
|||||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||||
public class InitialSetup {
|
public class InitialSetup {
|
||||||
|
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public InitialSetup(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() throws IOException {
|
public void init() throws IOException {
|
||||||
initUUIDKey();
|
initUUIDKey();
|
||||||
|
|
||||||
initSecretKey();
|
initSecretKey();
|
||||||
|
|
||||||
initEnableCSRFSecurity();
|
initEnableCSRFSecurity();
|
||||||
|
|
||||||
initLegalUrls();
|
initLegalUrls();
|
||||||
|
|
||||||
initSetAppVersion();
|
initSetAppVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initUUIDKey() throws IOException {
|
public void initUUIDKey() throws IOException {
|
||||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
|
uuid = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
||||||
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,8 @@ public class InitialSetup {
|
|||||||
public void initSecretKey() throws IOException {
|
public void initSecretKey() throws IOException {
|
||||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
|
secretKey = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
||||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,6 @@ public class InitialSetup {
|
|||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
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)) {
|
||||||
@@ -87,7 +87,6 @@ public class InitialSetup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initSetAppVersion() throws IOException {
|
public void initSetAppVersion() throws IOException {
|
||||||
|
|
||||||
String appVersion = "0.0.0";
|
String appVersion = "0.0.0";
|
||||||
Resource resource = new ClassPathResource("version.properties");
|
Resource resource = new ClassPathResource("version.properties");
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
@@ -95,7 +94,6 @@ public class InitialSetup {
|
|||||||
props.load(resource.getInputStream());
|
props.load(resource.getInputStream());
|
||||||
appVersion = props.getProperty("version");
|
appVersion = props.getProperty("version");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package stirling.software.SPDF.config;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
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.Configuration;
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
@@ -16,7 +15,11 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class LocaleConfiguration implements WebMvcConfigurer {
|
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public LocaleConfiguration(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
@@ -34,21 +37,17 @@ public class LocaleConfiguration implements WebMvcConfigurer {
|
|||||||
@Bean
|
@Bean
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
|
|
||||||
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
||||||
Locale defaultLocale =
|
Locale defaultLocale = // Fallback to UK locale if environment variable is not set
|
||||||
Locale.UK; // Fallback to UK locale if environment variable is not set
|
Locale.UK;
|
||||||
|
|
||||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
||||||
String tempLanguageTag = tempLocale.toLanguageTag();
|
String tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
|
|
||||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
defaultLocale = tempLocale;
|
defaultLocale = tempLocale;
|
||||||
} else {
|
} else {
|
||||||
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
|
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
|
||||||
tempLanguageTag = tempLocale.toLanguageTag();
|
tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
|
|
||||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
defaultLocale = tempLocale;
|
defaultLocale = tempLocale;
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +56,6 @@ public class LocaleConfiguration implements WebMvcConfigurer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slr.setDefaultLocale(defaultLocale);
|
slr.setDefaultLocale(defaultLocale);
|
||||||
return slr;
|
return slr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
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.Configuration;
|
||||||
|
|
||||||
@@ -15,15 +14,19 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class OpenApiConfig {
|
public class OpenApiConfig {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public OpenApiConfig(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
version = "1.0.0"; // default version if all else fails
|
// default version if all else fails
|
||||||
|
version = "1.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityScheme apiKeyScheme =
|
SecurityScheme apiKeyScheme =
|
||||||
new SecurityScheme()
|
new SecurityScheme()
|
||||||
.type(SecurityScheme.Type.APIKEY)
|
.type(SecurityScheme.Type.APIKEY)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
@@ -9,7 +8,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class WebMvcConfig implements WebMvcConfigurer {
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired private EndpointInterceptor endpointInterceptor;
|
private final EndpointInterceptor endpointInterceptor;
|
||||||
|
|
||||||
|
public WebMvcConfig(EndpointInterceptor endpointInterceptor) {
|
||||||
|
this.endpointInterceptor = endpointInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.List;
|
|||||||
import stirling.software.SPDF.utils.FileInfo;
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
public interface DatabaseBackupInterface {
|
public interface DatabaseBackupInterface {
|
||||||
|
|
||||||
void exportDatabase() throws IOException;
|
void exportDatabase() throws IOException;
|
||||||
|
|
||||||
boolean importDatabase();
|
boolean importDatabase();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package stirling.software.SPDF.config.security;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -15,8 +14,15 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
@Service
|
@Service
|
||||||
class AppUpdateAuthService implements ShowAdminInterface {
|
class AppUpdateAuthService implements ShowAdminInterface {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public AppUpdateAuthService(
|
||||||
|
UserRepository userRepository, ApplicationProperties applicationProperties) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getShowUpdateOnlyAdmins() {
|
public boolean getShowUpdateOnlyAdmins() {
|
||||||
@@ -24,24 +30,18 @@ class AppUpdateAuthService implements ShowAdminInterface {
|
|||||||
if (!showUpdate) {
|
if (!showUpdate) {
|
||||||
return showUpdate;
|
return showUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean showUpdateOnlyAdmin = applicationProperties.getSystem().getShowUpdateOnlyAdmin();
|
boolean showUpdateOnlyAdmin = applicationProperties.getSystem().getShowUpdateOnlyAdmin();
|
||||||
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
return !showUpdateOnlyAdmin;
|
return !showUpdateOnlyAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authentication.getName().equalsIgnoreCase("anonymousUser")) {
|
if (authentication.getName().equalsIgnoreCase("anonymousUser")) {
|
||||||
return !showUpdateOnlyAdmin;
|
return !showUpdateOnlyAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<User> user = userRepository.findByUsername(authentication.getName());
|
Optional<User> user = userRepository.findByUsername(authentication.getName());
|
||||||
if (user.isPresent() && showUpdateOnlyAdmin) {
|
if (user.isPresent() && showUpdateOnlyAdmin) {
|
||||||
return "ROLE_ADMIN".equals(user.get().getRolesAsString());
|
return "ROLE_ADMIN".equals(user.get().getRolesAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return showUpdate;
|
return showUpdate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,9 +219,9 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
// + response.encodeRedirectURL(redirect_url);
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
log.info("Google does not have a specific logout URL");
|
log.info("Google does not have a specific logout URL");
|
||||||
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
// response.sendRedirect(googleLogoutUrl);
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
// break;
|
// break;
|
||||||
default:
|
default:
|
||||||
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.util.Collection;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
@@ -20,9 +19,15 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
@Service
|
@Service
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
@Autowired private LoginAttemptService loginAttemptService;
|
private final LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
|
public CustomUserDetailsService(
|
||||||
|
UserRepository userRepository, LoginAttemptService loginAttemptService) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
@@ -33,16 +38,13 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
() ->
|
() ->
|
||||||
new UsernameNotFoundException(
|
new UsernameNotFoundException(
|
||||||
"No user found with username: " + username));
|
"No user found with username: " + username));
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.hasPassword()) {
|
if (!user.hasPassword()) {
|
||||||
throw new IllegalArgumentException("Password must not be null");
|
throw new IllegalArgumentException("Password must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new org.springframework.security.core.userdetails.User(
|
return new org.springframework.security.core.userdetails.User(
|
||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getPassword(),
|
user.getPassword(),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
@@ -25,7 +24,11 @@ import stirling.software.SPDF.utils.RequestUriUtils;
|
|||||||
@Component
|
@Component
|
||||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@Autowired @Lazy private UserService userService;
|
@Lazy private final UserService userService;
|
||||||
|
|
||||||
|
public FirstLoginFilter(@Lazy UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
@@ -34,16 +37,13 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
|||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
|
|
||||||
// Check if the request is for static resources
|
// Check if the request is for static resources
|
||||||
boolean isStaticResource = RequestUriUtils.isStaticResource(contextPath, requestURI);
|
boolean isStaticResource = RequestUriUtils.isStaticResource(contextPath, requestURI);
|
||||||
|
|
||||||
// If it's a static resource, just continue the filter chain and skip the logic below
|
// If it's a static resource, just continue the filter chain and skip the logic below
|
||||||
if (isStaticResource) {
|
if (isStaticResource) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Optional<User> user = userService.findByUsernameIgnoreCase(authentication.getName());
|
Optional<User> user = userService.findByUsernameIgnoreCase(authentication.getName());
|
||||||
@@ -55,12 +55,10 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
HttpSession session = request.getSession(true);
|
HttpSession session = request.getSession(true);
|
||||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
||||||
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
||||||
session.isNew(),
|
session.isNew(),
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.*;
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.ServletRequest;
|
|
||||||
import jakarta.servlet.ServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.config.security;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -16,11 +15,20 @@ import stirling.software.SPDF.model.Role;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class InitialSecuritySetup {
|
public class InitialSecuritySetup {
|
||||||
|
|
||||||
@Autowired private UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired private DatabaseBackupInterface databaseBackupHelper;
|
private final DatabaseBackupInterface databaseBackupHelper;
|
||||||
|
|
||||||
|
public InitialSecuritySetup(
|
||||||
|
UserService userService,
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
DatabaseBackupInterface databaseBackupHelper) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.databaseBackupHelper = databaseBackupHelper;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() throws IllegalArgumentException, IOException {
|
public void init() throws IllegalArgumentException, IOException {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.config.security;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -15,13 +14,20 @@ import stirling.software.SPDF.model.AttemptCounter;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoginAttemptService {
|
public class LoginAttemptService {
|
||||||
|
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private int MAX_ATTEMPT;
|
private int MAX_ATTEMPT;
|
||||||
|
|
||||||
private long ATTEMPT_INCREMENT_TIME;
|
private long ATTEMPT_INCREMENT_TIME;
|
||||||
|
|
||||||
private ConcurrentHashMap<String, AttemptCounter> attemptsCache;
|
private ConcurrentHashMap<String, AttemptCounter> attemptsCache;
|
||||||
|
|
||||||
private boolean isBlockedEnabled = true;
|
private boolean isBlockedEnabled = true;
|
||||||
|
|
||||||
|
public LoginAttemptService(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
MAX_ATTEMPT = applicationProperties.getSecurity().getLoginAttemptCount();
|
MAX_ATTEMPT = applicationProperties.getSecurity().getLoginAttemptCount();
|
||||||
@@ -46,7 +52,6 @@ public class LoginAttemptService {
|
|||||||
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
attemptCounter = new AttemptCounter();
|
attemptCounter = new AttemptCounter();
|
||||||
@@ -67,20 +72,18 @@ public class LoginAttemptService {
|
|||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attemptCounter.getAttemptCount() >= MAX_ATTEMPT;
|
return attemptCounter.getAttemptCount() >= MAX_ATTEMPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRemainingAttempts(String key) {
|
public int getRemainingAttempts(String key) {
|
||||||
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
||||||
return Integer.MAX_VALUE; // Arbitrarily high number if tracking is disabled
|
// Arbitrarily high number if tracking is disabled
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
return MAX_ATTEMPT;
|
return MAX_ATTEMPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MAX_ATTEMPT - attemptCounter.getAttemptCount();
|
return MAX_ATTEMPT - attemptCounter.getAttemptCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -63,6 +62,7 @@ import stirling.software.SPDF.model.provider.GithubProvider;
|
|||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@@ -71,38 +71,64 @@ import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
|||||||
@DependsOn("runningEE")
|
@DependsOn("runningEE")
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
|
@Lazy private final UserService userService;
|
||||||
|
|
||||||
|
@Qualifier("loginEnabled")
|
||||||
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Qualifier("runningEE")
|
||||||
|
private final boolean runningEE;
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
private final LoginAttemptService loginAttemptService;
|
||||||
|
private final FirstLoginFilter firstLoginFilter;
|
||||||
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
|
private final PersistentLoginRepository persistentLoginRepository;
|
||||||
|
|
||||||
|
// // Only Dev test
|
||||||
|
// @Bean
|
||||||
|
// public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
// return (web) ->
|
||||||
|
// web.ignoring()
|
||||||
|
// .requestMatchers(
|
||||||
|
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
||||||
|
// "/pdfjs-legacy/**");
|
||||||
|
// }
|
||||||
|
public SecurityConfiguration(
|
||||||
|
PersistentLoginRepository persistentLoginRepository,
|
||||||
|
CustomUserDetailsService userDetailsService,
|
||||||
|
@Lazy UserService userService,
|
||||||
|
@Qualifier("loginEnabled") boolean loginEnabledValue,
|
||||||
|
@Qualifier("runningEE") boolean runningEE,
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
UserAuthenticationFilter userAuthenticationFilter,
|
||||||
|
LoginAttemptService loginAttemptService,
|
||||||
|
FirstLoginFilter firstLoginFilter,
|
||||||
|
SessionPersistentRegistry sessionRegistry) {
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
|
this.runningEE = runningEE;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.userAuthenticationFilter = userAuthenticationFilter;
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
this.firstLoginFilter = firstLoginFilter;
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
this.persistentLoginRepository = persistentLoginRepository;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired @Lazy private UserService userService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("loginEnabled")
|
|
||||||
public boolean loginEnabledValue;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("runningEE")
|
|
||||||
public boolean runningEE;
|
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
|
||||||
|
|
||||||
@Autowired private LoginAttemptService loginAttemptService;
|
|
||||||
|
|
||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
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);
|
||||||
@@ -117,13 +143,11 @@ public class SecurityConfiguration {
|
|||||||
csrf.ignoringRequestMatchers(
|
csrf.ignoringRequestMatchers(
|
||||||
request -> {
|
request -> {
|
||||||
String apiKey = request.getHeader("X-API-KEY");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
|
|
||||||
// If there's no API key, don't ignore CSRF
|
// If there's no API key, don't ignore CSRF
|
||||||
// (return false)
|
// (return false)
|
||||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate API key using existing UserService
|
// Validate API key using existing UserService
|
||||||
try {
|
try {
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
@@ -152,7 +176,6 @@ public class SecurityConfiguration {
|
|||||||
.maxSessionsPreventsLogin(false)
|
.maxSessionsPreventsLogin(false)
|
||||||
.sessionRegistry(sessionRegistry)
|
.sessionRegistry(sessionRegistry)
|
||||||
.expiredUrl("/login?logout=true"));
|
.expiredUrl("/login?logout=true"));
|
||||||
|
|
||||||
http.authenticationProvider(daoAuthenticationProvider());
|
http.authenticationProvider(daoAuthenticationProvider());
|
||||||
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
||||||
http.logout(
|
http.logout(
|
||||||
@@ -161,18 +184,23 @@ public class SecurityConfiguration {
|
|||||||
.logoutSuccessHandler(
|
.logoutSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
new CustomLogoutSuccessHandler(applicationProperties))
|
||||||
.clearAuthentication(true)
|
.clearAuthentication(true)
|
||||||
.invalidateHttpSession(true) // Invalidate session
|
.invalidateHttpSession( // Invalidate session
|
||||||
|
true)
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
.deleteCookies("JSESSIONID", "remember-me"));
|
||||||
http.rememberMe(
|
http.rememberMe(
|
||||||
rememberMeConfigurer ->
|
rememberMeConfigurer -> // Use the configurator directly
|
||||||
rememberMeConfigurer // Use the configurator directly
|
rememberMeConfigurer
|
||||||
.tokenRepository(persistentTokenRepository())
|
.tokenRepository(persistentTokenRepository())
|
||||||
.tokenValiditySeconds(14 * 24 * 60 * 60) // 14 days
|
.tokenValiditySeconds( // 14 days
|
||||||
.userDetailsService(
|
14 * 24 * 60 * 60)
|
||||||
userDetailsService) // Your existing UserDetailsService
|
.userDetailsService( // Your existing UserDetailsService
|
||||||
.useSecureCookie(true) // Enable secure cookie
|
userDetailsService)
|
||||||
.rememberMeParameter("remember-me") // Form parameter name
|
.useSecureCookie( // Enable secure cookie
|
||||||
.rememberMeCookieName("remember-me") // Cookie name
|
true)
|
||||||
|
.rememberMeParameter( // Form parameter name
|
||||||
|
"remember-me")
|
||||||
|
.rememberMeCookieName( // Cookie name
|
||||||
|
"remember-me")
|
||||||
.alwaysRemember(false));
|
.alwaysRemember(false));
|
||||||
http.authorizeHttpRequests(
|
http.authorizeHttpRequests(
|
||||||
authz ->
|
authz ->
|
||||||
@@ -180,14 +208,12 @@ public class SecurityConfiguration {
|
|||||||
req -> {
|
req -> {
|
||||||
String uri = req.getRequestURI();
|
String uri = req.getRequestURI();
|
||||||
String contextPath = req.getContextPath();
|
String contextPath = req.getContextPath();
|
||||||
|
|
||||||
// Remove the context path from the URI
|
// Remove the context path from the URI
|
||||||
String trimmedUri =
|
String trimmedUri =
|
||||||
uri.startsWith(contextPath)
|
uri.startsWith(contextPath)
|
||||||
? uri.substring(
|
? uri.substring(
|
||||||
contextPath.length())
|
contextPath.length())
|
||||||
: uri;
|
: uri;
|
||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|| trimmedUri.startsWith("/saml2")
|
|| trimmedUri.startsWith("/saml2")
|
||||||
@@ -205,7 +231,6 @@ public class SecurityConfiguration {
|
|||||||
.permitAll()
|
.permitAll()
|
||||||
.anyRequest()
|
.anyRequest()
|
||||||
.authenticated());
|
.authenticated());
|
||||||
|
|
||||||
// Handle User/Password Logins
|
// Handle User/Password Logins
|
||||||
if (applicationProperties.getSecurity().isUserPass()) {
|
if (applicationProperties.getSecurity().isUserPass()) {
|
||||||
http.formLogin(
|
http.formLogin(
|
||||||
@@ -221,27 +246,26 @@ public class SecurityConfiguration {
|
|||||||
.defaultSuccessUrl("/")
|
.defaultSuccessUrl("/")
|
||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
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 autocreated 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(
|
||||||
new CustomOAuth2AuthenticationSuccessHandler(
|
new CustomOAuth2AuthenticationSuccessHandler(
|
||||||
loginAttemptService,
|
loginAttemptService,
|
||||||
applicationProperties,
|
applicationProperties,
|
||||||
userService))
|
userService))
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomOAuth2AuthenticationFailureHandler())
|
new CustomOAuth2AuthenticationFailureHandler())
|
||||||
// Add existing Authorities from the database
|
. // Add existing Authorities from the database
|
||||||
.userInfoEndpoint(
|
userInfoEndpoint(
|
||||||
userInfoEndpoint ->
|
userInfoEndpoint ->
|
||||||
userInfoEndpoint
|
userInfoEndpoint
|
||||||
.oidcUserService(
|
.oidcUserService(
|
||||||
@@ -253,15 +277,14 @@ public class SecurityConfiguration {
|
|||||||
userAuthoritiesMapper()))
|
userAuthoritiesMapper()))
|
||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Activ()) { // && runningEE
|
if (applicationProperties.getSecurity().isSaml2Activ()) {
|
||||||
|
// && runningEE
|
||||||
// Configure the authentication provider
|
// Configure the authentication provider
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
authenticationProvider.setResponseAuthenticationConverter(
|
authenticationProvider.setResponseAuthenticationConverter(
|
||||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
new CustomSaml2ResponseAuthenticationConverter(userService));
|
||||||
|
|
||||||
http.authenticationProvider(authenticationProvider)
|
http.authenticationProvider(authenticationProvider)
|
||||||
.saml2Login(
|
.saml2Login(
|
||||||
saml2 -> {
|
saml2 -> {
|
||||||
@@ -287,7 +310,6 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
// CookieCsrfTokenRepository cookieRepo =
|
// CookieCsrfTokenRepository cookieRepo =
|
||||||
@@ -302,7 +324,6 @@ public class SecurityConfiguration {
|
|||||||
// }
|
// }
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,17 +334,14 @@ public class SecurityConfiguration {
|
|||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
if (registrations.isEmpty()) {
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
log.error("At least one OAuth2 provider must be configured");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +384,6 @@ public class SecurityConfiguration {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
? Optional.of(
|
? Optional.of(
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
@@ -381,7 +398,6 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -443,19 +459,15 @@ public class SecurityConfiguration {
|
|||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
|
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
||||||
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
Resource certificateResource = samlConf.getSpCert();
|
||||||
|
|
||||||
Saml2X509Credential signingCredential =
|
Saml2X509Credential signingCredential =
|
||||||
new Saml2X509Credential(
|
new Saml2X509Credential(
|
||||||
CertificateUtils.readPrivateKey(privateKeyResource),
|
CertificateUtils.readPrivateKey(privateKeyResource),
|
||||||
CertificateUtils.readCertificate(certificateResource),
|
CertificateUtils.readCertificate(certificateResource),
|
||||||
Saml2X509CredentialType.SIGNING);
|
Saml2X509CredentialType.SIGNING);
|
||||||
|
|
||||||
RelyingPartyRegistration rp =
|
RelyingPartyRegistration rp =
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
.signingX509Credentials(c -> c.add(signingCredential))
|
.signingX509Credentials(c -> c.add(signingCredential))
|
||||||
@@ -470,7 +482,6 @@ public class SecurityConfiguration {
|
|||||||
Saml2MessageBinding.POST)
|
Saml2MessageBinding.POST)
|
||||||
.wantAuthnRequestsSigned(true))
|
.wantAuthnRequestsSigned(true))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,10 +497,8 @@ public class SecurityConfiguration {
|
|||||||
resolver.setAuthnRequestCustomizer(
|
resolver.setAuthnRequestCustomizer(
|
||||||
customizer -> {
|
customizer -> {
|
||||||
log.debug("Customizing SAML Authentication request");
|
log.debug("Customizing SAML Authentication request");
|
||||||
|
|
||||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
||||||
|
|
||||||
if (authnRequest.getID() == null) {
|
if (authnRequest.getID() == null) {
|
||||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
||||||
}
|
}
|
||||||
@@ -500,16 +509,13 @@ public class SecurityConfiguration {
|
|||||||
authnRequest.getIssuer() != null
|
authnRequest.getIssuer() != null
|
||||||
? authnRequest.getIssuer().getValue()
|
? authnRequest.getIssuer().getValue()
|
||||||
: "null");
|
: "null");
|
||||||
|
|
||||||
HttpServletRequest request = customizer.getRequest();
|
HttpServletRequest request = customizer.getRequest();
|
||||||
|
|
||||||
// Log HTTP request details
|
// Log HTTP request details
|
||||||
log.debug("HTTP Request Method: {}", request.getMethod());
|
log.debug("HTTP Request Method: {}", request.getMethod());
|
||||||
log.debug("Request URI: {}", request.getRequestURI());
|
log.debug("Request URI: {}", request.getRequestURI());
|
||||||
log.debug("Request URL: {}", request.getRequestURL().toString());
|
log.debug("Request URL: {}", request.getRequestURL().toString());
|
||||||
log.debug("Query String: {}", request.getQueryString());
|
log.debug("Query String: {}", request.getQueryString());
|
||||||
log.debug("Remote Address: {}", request.getRemoteAddr());
|
log.debug("Remote Address: {}", request.getRemoteAddr());
|
||||||
|
|
||||||
// Log headers
|
// Log headers
|
||||||
Collections.list(request.getHeaderNames())
|
Collections.list(request.getHeaderNames())
|
||||||
.forEach(
|
.forEach(
|
||||||
@@ -519,24 +525,20 @@ public class SecurityConfiguration {
|
|||||||
headerName,
|
headerName,
|
||||||
request.getHeader(headerName));
|
request.getHeader(headerName));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log SAML specific parameters
|
// Log SAML specific parameters
|
||||||
log.debug("SAML Request Parameters:");
|
log.debug("SAML Request Parameters:");
|
||||||
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
||||||
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
||||||
|
|
||||||
// Log session debugrmation if exists
|
// Log session debugrmation if exists
|
||||||
if (request.getSession(false) != null) {
|
if (request.getSession(false) != null) {
|
||||||
log.debug("Session ID: {}", request.getSession().getId());
|
log.debug("Session ID: {}", request.getSession().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log any assertions consumer service details if present
|
// Log any assertions consumer service details if present
|
||||||
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"AssertionConsumerServiceURL: {}",
|
"AssertionConsumerServiceURL: {}",
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
authnRequest.getAssertionConsumerServiceURL());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log NameID policy if present
|
// Log NameID policy if present
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
if (authnRequest.getNameIDPolicy() != null) {
|
||||||
log.debug(
|
log.debug(
|
||||||
@@ -566,12 +568,10 @@ public class SecurityConfiguration {
|
|||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
return (authorities) -> {
|
return (authorities) -> {
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
|
||||||
authorities.forEach(
|
authorities.forEach(
|
||||||
authority -> {
|
authority -> {
|
||||||
// Add existing OAUTH2 Authorities
|
// Add existing OAUTH2 Authorities
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||||
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
// Add Authorities from database for existing user, if user is present.
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||||
String useAsUsername =
|
String useAsUsername =
|
||||||
@@ -598,27 +598,18 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
// Example limit TODO add config level
|
||||||
|
int maxRequestsPerIp = 1000000;
|
||||||
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PersistentTokenRepository persistentTokenRepository() {
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
return new JPATokenRepositoryImpl();
|
return new JPATokenRepositoryImpl(persistentLoginRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public boolean activSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Only Dev test
|
|
||||||
// @Bean
|
|
||||||
// public WebSecurityCustomizer webSecurityCustomizer() {
|
|
||||||
// return (web) ->
|
|
||||||
// web.ignoring()
|
|
||||||
// .requestMatchers(
|
|
||||||
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
|
||||||
// "/pdfjs-legacy/**");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import java.time.Duration;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -31,13 +29,15 @@ import stirling.software.SPDF.model.Role;
|
|||||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired private UserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("rateLimit")
|
@Qualifier("rateLimit")
|
||||||
public boolean rateLimit;
|
private final boolean rateLimit;
|
||||||
|
|
||||||
|
public UserBasedRateLimitingFilter(@Qualifier("rateLimit") boolean rateLimit) {
|
||||||
|
this.rateLimit = rateLimit;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
@@ -48,21 +48,18 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
if (!"POST".equalsIgnoreCase(method)) {
|
if (!"POST".equalsIgnoreCase(method)) {
|
||||||
// If the request is not a POST, just pass it through without rate limiting
|
// If the request is not a POST, just pass it through without rate limiting
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String identifier = null;
|
String identifier = null;
|
||||||
|
|
||||||
// Check for API key in the request headers
|
// Check for API key in the request headers
|
||||||
String apiKey = request.getHeader("X-API-KEY");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
identifier =
|
identifier = // Prefix to distinguish between API keys and usernames
|
||||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
"API_KEY_" + apiKey;
|
||||||
} else {
|
} else {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
@@ -70,15 +67,12 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
identifier = userDetails.getUsername();
|
identifier = userDetails.getUsername();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If neither API key nor an authenticated user is present, use IP address
|
// If neither API key nor an authenticated user is present, use IP address
|
||||||
if (identifier == null) {
|
if (identifier == null) {
|
||||||
identifier = request.getRemoteAddr();
|
identifier = request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Role userRole =
|
Role userRole =
|
||||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
if (request.getHeader("X-API-KEY") != null) {
|
if (request.getHeader("X-API-KEY") != null) {
|
||||||
// It's an API call
|
// It's an API call
|
||||||
processRequest(
|
processRequest(
|
||||||
@@ -123,7 +117,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
||||||
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
||||||
|
|
||||||
if (probe.isConsumed()) {
|
if (probe.isConsumed()) {
|
||||||
response.setHeader(
|
response.setHeader(
|
||||||
"X-Rate-Limit-Remaining",
|
"X-Rate-Limit-Remaining",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
@@ -25,11 +24,7 @@ import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
|||||||
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.ApplicationProperties;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
|
||||||
import stirling.software.SPDF.model.Authority;
|
|
||||||
import stirling.software.SPDF.model.Role;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@@ -37,19 +32,36 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class UserService implements UserServiceInterface {
|
public class UserService implements UserServiceInterface {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
@Autowired private AuthorityRepository authorityRepository;
|
private final AuthorityRepository authorityRepository;
|
||||||
|
|
||||||
@Autowired private PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Autowired private MessageSource messageSource;
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
private final DatabaseBackupInterface databaseBackupHelper;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public UserService(
|
||||||
|
UserRepository userRepository,
|
||||||
|
AuthorityRepository authorityRepository,
|
||||||
|
PasswordEncoder passwordEncoder,
|
||||||
|
MessageSource messageSource,
|
||||||
|
SessionPersistentRegistry sessionRegistry,
|
||||||
|
DatabaseBackupInterface databaseBackupHelper,
|
||||||
|
ApplicationProperties applicationProperties) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.authorityRepository = authorityRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
this.databaseBackupHelper = databaseBackupHelper;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void migrateOauth2ToSSO() {
|
public void migrateOauth2ToSSO() {
|
||||||
@@ -84,13 +96,11 @@ public class UserService implements UserServiceInterface {
|
|||||||
if (!user.isPresent()) {
|
if (!user.isPresent()) {
|
||||||
throw new UsernameNotFoundException("API key is not valid");
|
throw new UsernameNotFoundException("API key is not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the user into an Authentication object
|
// Convert the user into an Authentication object
|
||||||
return new UsernamePasswordAuthenticationToken(
|
return new UsernamePasswordAuthenticationToken( // principal (typically the user)
|
||||||
user, // principal (typically the user)
|
user, // credentials (we don't expose the password or API key here)
|
||||||
null, // credentials (we don't expose the password or API key here)
|
null, // user's authorities (roles/permissions)
|
||||||
getAuthorities(user.get()) // user's authorities (roles/permissions)
|
getAuthorities(user.get()));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
||||||
@@ -104,7 +114,8 @@ public class UserService implements UserServiceInterface {
|
|||||||
String apiKey;
|
String apiKey;
|
||||||
do {
|
do {
|
||||||
apiKey = UUID.randomUUID().toString();
|
apiKey = UUID.randomUUID().toString();
|
||||||
} while (userRepository.findByApiKey(apiKey).isPresent()); // Ensure uniqueness
|
} while ( // Ensure uniqueness
|
||||||
|
userRepository.findByApiKey(apiKey).isPresent());
|
||||||
return apiKey;
|
return apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +129,8 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public User refreshApiKeyForUser(String username) {
|
public User refreshApiKeyForUser(String username) {
|
||||||
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
// reuse the add API key method for refreshing
|
||||||
|
return addApiKeyToUser(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApiKeyForUser(String username) {
|
public String getApiKeyForUser(String username) {
|
||||||
@@ -138,11 +150,11 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public Optional<User> loadUserByApiKey(String apiKey) {
|
public Optional<User> loadUserByApiKey(String apiKey) {
|
||||||
Optional<User> user = userRepository.findByApiKey(apiKey);
|
Optional<User> user = userRepository.findByApiKey(apiKey);
|
||||||
|
|
||||||
if (user.isPresent()) {
|
if (user.isPresent()) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
return null; // or throw an exception
|
// or throw an exception
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateApiKeyForUser(String username, String apiKey) {
|
public boolean validateApiKeyForUser(String username, String apiKey) {
|
||||||
@@ -240,14 +252,12 @@ public class UserService implements UserServiceInterface {
|
|||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
Map<String, String> settingsMap = user.getSettings();
|
Map<String, String> settingsMap = user.getSettings();
|
||||||
|
|
||||||
if (settingsMap == null) {
|
if (settingsMap == null) {
|
||||||
settingsMap = new HashMap<>();
|
settingsMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
settingsMap.clear();
|
settingsMap.clear();
|
||||||
settingsMap.putAll(updates);
|
settingsMap.putAll(updates);
|
||||||
user.setSettings(settingsMap);
|
user.setSettings(settingsMap);
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseBackupHelper.exportDatabase();
|
||||||
}
|
}
|
||||||
@@ -316,12 +326,9 @@ public class UserService implements UserServiceInterface {
|
|||||||
boolean isValidEmail =
|
boolean isValidEmail =
|
||||||
username.matches(
|
username.matches(
|
||||||
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
||||||
|
|
||||||
List<String> notAllowedUserList = new ArrayList<>();
|
List<String> notAllowedUserList = new ArrayList<>();
|
||||||
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
||||||
|
|
||||||
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
||||||
|
|
||||||
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
|
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +381,6 @@ 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) {
|
||||||
return ((UserDetails) principal).getUsername();
|
return ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
@@ -397,7 +403,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
String username = "CUSTOM_API_USER";
|
String username = "CUSTOM_API_USER";
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
if (!existingUser.isPresent()) {
|
if (!existingUser.isPresent()) {
|
||||||
// Create new user with API role
|
// Create new user with API role
|
||||||
User user = new User();
|
User user = new User();
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ 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.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.sql.Connection;
|
import java.sql.*;
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ package stirling.software.SPDF.config.security.database;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ScheduledTasks {
|
public class ScheduledTasks {
|
||||||
|
|
||||||
@Autowired private DatabaseBackupHelper databaseBackupService;
|
private final DatabaseBackupHelper databaseBackupService;
|
||||||
|
|
||||||
|
public ScheduledTasks(DatabaseBackupHelper databaseBackupService) {
|
||||||
|
this.databaseBackupService = databaseBackupService;
|
||||||
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0 0 * * ?")
|
@Scheduled(cron = "0 0 0 * * ?")
|
||||||
public void performBackup() throws IOException {
|
public void performBackup() throws IOException {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package stirling.software.SPDF.config.security.oauth2;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||||
@@ -13,6 +11,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
|||||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@@ -20,6 +19,7 @@ import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||||
|
|
||||||
private final OidcUserService delegate = new OidcUserService();
|
private final OidcUserService delegate = new OidcUserService();
|
||||||
@@ -30,8 +30,6 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
|
|
||||||
private ApplicationProperties applicationProperties;
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CustomOAuth2UserService.class);
|
|
||||||
|
|
||||||
public CustomOAuth2UserService(
|
public CustomOAuth2UserService(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
@@ -82,10 +80,10 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
user.getUserInfo(),
|
user.getUserInfo(),
|
||||||
usernameAttribute);
|
usernameAttribute);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
logger.error("Error loading OIDC user: {}", e.getMessage());
|
log.error("Error loading OIDC user: {}", e.getMessage());
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Unexpected error loading OIDC user", e);
|
log.error("Unexpected error loading OIDC user", e);
|
||||||
throw new OAuth2AuthenticationException("Unexpected error during authentication");
|
throw new OAuth2AuthenticationException("Unexpected error during authentication");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package stirling.software.SPDF.config.security.session;
|
package stirling.software.SPDF.config.security.session;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.session.SessionInformation;
|
import org.springframework.security.core.session.SessionInformation;
|
||||||
|
|||||||
@@ -5,19 +5,22 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.security.core.session.SessionInformation;
|
import org.springframework.security.core.session.SessionInformation;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SessionScheduled {
|
public class SessionScheduled {
|
||||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
|
||||||
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
|
||||||
|
public SessionScheduled(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0/5 * * * ?")
|
@Scheduled(cron = "0 0/5 * * * ?")
|
||||||
public void expireSessions() {
|
public void expireSessions() {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
|
|
||||||
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
|
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
|
||||||
List<SessionInformation> sessionInformations =
|
List<SessionInformation> sessionInformations =
|
||||||
sessionPersistentRegistry.getAllSessions(principal, false);
|
sessionPersistentRegistry.getAllSessions(principal, false);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -18,35 +17,35 @@ import stirling.software.SPDF.service.LanguageService;
|
|||||||
@RequestMapping("/js")
|
@RequestMapping("/js")
|
||||||
public class AdditionalLanguageJsController {
|
public class AdditionalLanguageJsController {
|
||||||
|
|
||||||
@Autowired private LanguageService languageService;
|
private final LanguageService languageService;
|
||||||
|
|
||||||
|
public AdditionalLanguageJsController(LanguageService languageService) {
|
||||||
|
this.languageService = languageService;
|
||||||
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
@GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript")
|
@GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript")
|
||||||
public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
|
public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
|
||||||
List<String> supportedLanguages = languageService.getSupportedLanguages();
|
List<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||||
|
|
||||||
response.setContentType("application/javascript");
|
response.setContentType("application/javascript");
|
||||||
PrintWriter writer = response.getWriter();
|
PrintWriter writer = response.getWriter();
|
||||||
|
|
||||||
// Erstelle das JavaScript dynamisch
|
// Erstelle das JavaScript dynamisch
|
||||||
writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";");
|
writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";");
|
||||||
|
|
||||||
// Generiere die `getDetailedLanguageCode`-Funktion
|
// Generiere die `getDetailedLanguageCode`-Funktion
|
||||||
writer.println(
|
writer.println(
|
||||||
"""
|
"""
|
||||||
function getDetailedLanguageCode() {
|
function getDetailedLanguageCode() {
|
||||||
const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
|
const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
|
||||||
for (let lang of userLanguages) {
|
for (let lang of userLanguages) {
|
||||||
let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_')));
|
let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_')));
|
||||||
if (matchedLang) {
|
if (matchedLang) {
|
||||||
return matchedLang;
|
return matchedLang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback
|
||||||
|
return "en_GB";
|
||||||
}
|
}
|
||||||
}
|
""");
|
||||||
// Fallback
|
|
||||||
return "en_GB";
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
writer.flush();
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -33,8 +31,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class CropController {
|
public class CropController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
private final PostHogService postHogService;
|
private final PostHogService postHogService;
|
||||||
|
|||||||
@@ -8,23 +8,19 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -34,19 +30,25 @@ import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
|
|||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/api/v1/database")
|
@RequestMapping("/api/v1/database")
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@Tag(name = "Database", description = "Database APIs")
|
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
|
||||||
public class DatabaseController {
|
public class DatabaseController {
|
||||||
|
|
||||||
@Autowired DatabaseBackupHelper databaseBackupHelper;
|
private final DatabaseBackupHelper databaseBackupHelper;
|
||||||
|
|
||||||
|
public DatabaseController(DatabaseBackupHelper databaseBackupHelper) {
|
||||||
|
this.databaseBackupHelper = databaseBackupHelper;
|
||||||
|
}
|
||||||
|
|
||||||
@Hidden
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "import-database")
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Import database backup",
|
summary = "Import a database backup file",
|
||||||
description = "This endpoint imports a database backup from a SQL file.")
|
description = "Uploads and imports a database backup SQL file.")
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "import-database")
|
||||||
public String importDatabase(
|
public String importDatabase(
|
||||||
@RequestParam("fileInput") MultipartFile file, RedirectAttributes redirectAttributes)
|
@Parameter(description = "SQL file to import", required = true)
|
||||||
throws IllegalArgumentException, IOException {
|
@RequestParam("fileInput")
|
||||||
|
MultipartFile file,
|
||||||
|
RedirectAttributes redirectAttributes)
|
||||||
|
throws IOException {
|
||||||
if (file == null || file.isEmpty()) {
|
if (file == null || file.isEmpty()) {
|
||||||
redirectAttributes.addAttribute("error", "fileNullOrEmpty");
|
redirectAttributes.addAttribute("error", "fileNullOrEmpty");
|
||||||
return "redirect:/database";
|
return "redirect:/database";
|
||||||
@@ -69,13 +71,17 @@ public class DatabaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
|
@Operation(
|
||||||
|
summary = "Import database backup by filename",
|
||||||
|
description = "Imports a database backup file from the server using its file name.")
|
||||||
@GetMapping("/import-database-file/{fileName}")
|
@GetMapping("/import-database-file/{fileName}")
|
||||||
public String importDatabaseFromBackupUI(@PathVariable String fileName)
|
public String importDatabaseFromBackupUI(
|
||||||
throws IllegalArgumentException, IOException {
|
@Parameter(description = "Name of the file to import", required = true) @PathVariable
|
||||||
|
String fileName)
|
||||||
|
throws IOException {
|
||||||
if (fileName == null || fileName.isEmpty()) {
|
if (fileName == null || fileName.isEmpty()) {
|
||||||
return "redirect:/database?error=fileNullOrEmpty";
|
return "redirect:/database?error=fileNullOrEmpty";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the file exists in the backup list
|
// Check if the file exists in the backup list
|
||||||
boolean fileExists =
|
boolean fileExists =
|
||||||
databaseBackupHelper.getBackupList().stream()
|
databaseBackupHelper.getBackupList().stream()
|
||||||
@@ -93,12 +99,13 @@ public class DatabaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
@GetMapping("/delete/{fileName}")
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Delete a database backup file",
|
summary = "Delete a database backup file",
|
||||||
description =
|
description = "Deletes a specified database backup file from the server.")
|
||||||
"This endpoint deletes a database backup file with the specified file name.")
|
@GetMapping("/delete/{fileName}")
|
||||||
public String deleteFile(@PathVariable String fileName) {
|
public String deleteFile(
|
||||||
|
@Parameter(description = "Name of the file to delete", required = true) @PathVariable
|
||||||
|
String fileName) {
|
||||||
if (fileName == null || fileName.isEmpty()) {
|
if (fileName == null || fileName.isEmpty()) {
|
||||||
throw new IllegalArgumentException("File must not be null or empty");
|
throw new IllegalArgumentException("File must not be null or empty");
|
||||||
}
|
}
|
||||||
@@ -117,12 +124,13 @@ public class DatabaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
@GetMapping("/download/{fileName}")
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Download a database backup file",
|
summary = "Download a database backup file",
|
||||||
description =
|
description = "Downloads the specified database backup file from the server.")
|
||||||
"This endpoint downloads a database backup file with the specified file name.")
|
@GetMapping("/download/{fileName}")
|
||||||
public ResponseEntity<?> downloadFile(@PathVariable String fileName) {
|
public ResponseEntity<?> downloadFile(
|
||||||
|
@Parameter(description = "Name of the file to download", required = true) @PathVariable
|
||||||
|
String fileName) {
|
||||||
if (fileName == null || fileName.isEmpty()) {
|
if (fileName == null || fileName.isEmpty()) {
|
||||||
throw new IllegalArgumentException("File must not be null or empty");
|
throw new IllegalArgumentException("File must not be null or empty");
|
||||||
}
|
}
|
||||||
@@ -141,4 +149,22 @@ public class DatabaseController {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Create a database backup",
|
||||||
|
description =
|
||||||
|
"This endpoint triggers the creation of a database backup and redirects to the"
|
||||||
|
+ " database management page.")
|
||||||
|
@GetMapping("/createDatabaseBackup")
|
||||||
|
public String createDatabaseBackup() {
|
||||||
|
try {
|
||||||
|
log.info("Starting database backup creation...");
|
||||||
|
databaseBackupHelper.exportDatabase();
|
||||||
|
log.info("Database backup successfully created.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error creating database backup: {}", e.getMessage(), e);
|
||||||
|
return "redirect:/database?error=" + e.getMessage();
|
||||||
|
}
|
||||||
|
return "redirect:/database?infoMessage=backupCreated";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -33,18 +31,18 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Slf4j
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MergeController {
|
public class MergeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -184,7 +182,7 @@ public class MergeController {
|
|||||||
baos.toByteArray(), mergedFileName); // Return the modified PDF
|
baos.toByteArray(), mergedFileName); // Return the modified PDF
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Error in merge pdf process", ex);
|
log.error("Error in merge pdf process", ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
for (File file : filesToDelete) {
|
for (File file : filesToDelete) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -12,8 +12,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -35,8 +33,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MultiPageLayoutController {
|
public class MultiPageLayoutController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import java.io.IOException;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import java.util.List;
|
|||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.SortTypes;
|
import stirling.software.SPDF.model.SortTypes;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
||||||
@@ -31,11 +30,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RearrangePagesPDFController {
|
public class RearrangePagesPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -202,7 +200,7 @@ public class RearrangePagesPDFController {
|
|||||||
throw new IllegalArgumentException("Unsupported custom mode");
|
throw new IllegalArgumentException("Unsupported custom mode");
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
logger.error("Unsupported custom mode", e);
|
log.error("Unsupported custom mode", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,8 +228,8 @@ public class RearrangePagesPDFController {
|
|||||||
} else {
|
} else {
|
||||||
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
||||||
}
|
}
|
||||||
logger.info("newPageOrder = " + newPageOrder);
|
log.info("newPageOrder = " + newPageOrder);
|
||||||
logger.info("totalPages = " + totalPages);
|
log.info("totalPages = " + totalPages);
|
||||||
// Create a new list to hold the pages in the new order
|
// Create a new list to hold the pages in the new order
|
||||||
List<PDPage> newPages = new ArrayList<>();
|
List<PDPage> newPages = new ArrayList<>();
|
||||||
for (int i = 0; i < newPageOrder.size(); i++) {
|
for (int i = 0; i < newPageOrder.size(); i++) {
|
||||||
@@ -254,7 +252,7 @@ public class RearrangePagesPDFController {
|
|||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_rearranged.pdf");
|
+ "_rearranged.pdf");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed rearranging documents", e);
|
log.error("Failed rearranging documents", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import java.io.IOException;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -28,8 +26,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RotationController {
|
public class RotationController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -36,8 +34,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class ScalePagesController {
|
public class ScalePagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package stirling.software.SPDF.controller.api;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -20,7 +21,11 @@ import stirling.software.SPDF.utils.GeneralUtils;
|
|||||||
@Hidden
|
@Hidden
|
||||||
public class SettingsController {
|
public class SettingsController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public SettingsController(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/update-enable-analytics")
|
@PostMapping("/update-enable-analytics")
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -32,7 +37,6 @@ public class SettingsController {
|
|||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||||
|
|
||||||
return ResponseEntity.ok("Updated");
|
return ResponseEntity.ok("Updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -28,16 +26,17 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -73,7 +72,7 @@ public class SplitPDFController {
|
|||||||
pageNumbers.add(totalPages - 1);
|
pageNumbers.add(totalPages - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
log.info(
|
||||||
"Splitting PDF into pages: {}",
|
"Splitting PDF into pages: {}",
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ public class SplitPDFController {
|
|||||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
splitDocument.addPage(page);
|
splitDocument.addPage(page);
|
||||||
logger.info("Adding page {} to split document", i);
|
log.info("Adding page {} to split document", i);
|
||||||
}
|
}
|
||||||
previousPageNumber = splitPoint + 1;
|
previousPageNumber = splitPoint + 1;
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ public class SplitPDFController {
|
|||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
splitDocumentsBoas.add(baos);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
log.error("Failed splitting documents and saving them", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,15 +123,14 @@ public class SplitPDFController {
|
|||||||
zipOut.write(pdf);
|
zipOut.write(pdf);
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
|
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
log.info("Wrote split document {} to zip file", fileName);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed writing to zip", e);
|
log.error("Failed writing to zip", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
log.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
"Successfully created zip file with split documents: {}", zipFile.toString());
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
@@ -159,7 +157,7 @@ public class SplitPDFController {
|
|||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error while cleaning up resources", e);
|
log.error("Error while cleaning up resources", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -32,6 +30,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
import stirling.software.SPDF.model.PdfMetadata;
|
||||||
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
||||||
import stirling.software.SPDF.service.PdfMetadataService;
|
import stirling.software.SPDF.service.PdfMetadataService;
|
||||||
@@ -39,12 +38,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPdfByChaptersController {
|
public class SplitPdfByChaptersController {
|
||||||
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
|
|
||||||
|
|
||||||
private final PdfMetadataService pdfMetadataService;
|
private final PdfMetadataService pdfMetadataService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -52,120 +49,6 @@ public class SplitPdfByChaptersController {
|
|||||||
this.pdfMetadataService = pdfMetadataService;
|
this.pdfMetadataService = pdfMetadataService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
|
||||||
@Operation(
|
|
||||||
summary = "Split PDFs by Chapters",
|
|
||||||
description = "Splits a PDF into chapters and returns a ZIP file.")
|
|
||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
|
||||||
throws Exception {
|
|
||||||
MultipartFile file = request.getFileInput();
|
|
||||||
PDDocument sourceDocument = null;
|
|
||||||
Path zipFile = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean includeMetadata = request.getIncludeMetadata();
|
|
||||||
Integer bookmarkLevel =
|
|
||||||
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
|
||||||
if (bookmarkLevel < 0) {
|
|
||||||
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
|
||||||
}
|
|
||||||
sourceDocument = Loader.loadPDF(file.getBytes());
|
|
||||||
|
|
||||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
|
||||||
|
|
||||||
if (outline == null) {
|
|
||||||
logger.warn("No outline found for {}", file.getOriginalFilename());
|
|
||||||
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
|
||||||
}
|
|
||||||
List<Bookmark> bookmarks = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
bookmarks =
|
|
||||||
extractOutlineItems(
|
|
||||||
sourceDocument,
|
|
||||||
outline.getFirstChild(),
|
|
||||||
bookmarks,
|
|
||||||
outline.getFirstChild().getNextSibling(),
|
|
||||||
0,
|
|
||||||
bookmarkLevel);
|
|
||||||
// to handle last page edge case
|
|
||||||
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
|
||||||
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Unable to extract outline items", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
|
||||||
.body("Unable to extract outline items".getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean allowDuplicates = request.getAllowDuplicates();
|
|
||||||
if (!allowDuplicates) {
|
|
||||||
/*
|
|
||||||
duplicates are generated when multiple bookmarks correspond to the same page,
|
|
||||||
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
|
||||||
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
|
||||||
*/
|
|
||||||
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
|
||||||
}
|
|
||||||
for (Bookmark bookmark : bookmarks) {
|
|
||||||
logger.info(
|
|
||||||
"{}::::{} to {}",
|
|
||||||
bookmark.getTitle(),
|
|
||||||
bookmark.getStartPage(),
|
|
||||||
bookmark.getEndPage());
|
|
||||||
}
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas =
|
|
||||||
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
|
||||||
|
|
||||||
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
|
||||||
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
|
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
sourceDocument.close();
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (sourceDocument != null) {
|
|
||||||
sourceDocument.close();
|
|
||||||
}
|
|
||||||
if (zipFile != null) {
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error while cleaning up resources", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
|
||||||
String mergedTitle = "";
|
|
||||||
List<Bookmark> chaptersToBeRemoved = new ArrayList<>();
|
|
||||||
for (Bookmark bookmark : bookmarks) {
|
|
||||||
if (bookmark.getStartPage() == bookmark.getEndPage()) {
|
|
||||||
mergedTitle = mergedTitle.concat(bookmark.getTitle().concat(" "));
|
|
||||||
chaptersToBeRemoved.add(bookmark);
|
|
||||||
} else {
|
|
||||||
if (!mergedTitle.isEmpty()) {
|
|
||||||
if (mergedTitle.length() > 255) {
|
|
||||||
mergedTitle = mergedTitle.substring(0, 253) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmarks.set(
|
|
||||||
bookmarks.indexOf(bookmark),
|
|
||||||
new Bookmark(
|
|
||||||
mergedTitle, bookmark.getStartPage(), bookmark.getEndPage()));
|
|
||||||
}
|
|
||||||
mergedTitle = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bookmarks.removeAll(chaptersToBeRemoved);
|
|
||||||
return bookmarks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Bookmark> extractOutlineItems(
|
private static List<Bookmark> extractOutlineItems(
|
||||||
PDDocument sourceDocument,
|
PDDocument sourceDocument,
|
||||||
PDOutlineItem current,
|
PDOutlineItem current,
|
||||||
@@ -234,6 +117,120 @@ public class SplitPdfByChaptersController {
|
|||||||
return bookmarks;
|
return bookmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
||||||
|
@Operation(
|
||||||
|
summary = "Split PDFs by Chapters",
|
||||||
|
description = "Splits a PDF into chapters and returns a ZIP file.")
|
||||||
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
||||||
|
throws Exception {
|
||||||
|
MultipartFile file = request.getFileInput();
|
||||||
|
PDDocument sourceDocument = null;
|
||||||
|
Path zipFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean includeMetadata = request.getIncludeMetadata();
|
||||||
|
Integer bookmarkLevel =
|
||||||
|
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
||||||
|
if (bookmarkLevel < 0) {
|
||||||
|
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
||||||
|
}
|
||||||
|
sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
|
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||||
|
|
||||||
|
if (outline == null) {
|
||||||
|
log.warn("No outline found for {}", file.getOriginalFilename());
|
||||||
|
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
||||||
|
}
|
||||||
|
List<Bookmark> bookmarks = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
bookmarks =
|
||||||
|
extractOutlineItems(
|
||||||
|
sourceDocument,
|
||||||
|
outline.getFirstChild(),
|
||||||
|
bookmarks,
|
||||||
|
outline.getFirstChild().getNextSibling(),
|
||||||
|
0,
|
||||||
|
bookmarkLevel);
|
||||||
|
// to handle last page edge case
|
||||||
|
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
||||||
|
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unable to extract outline items", e);
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body("Unable to extract outline items".getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allowDuplicates = request.getAllowDuplicates();
|
||||||
|
if (!allowDuplicates) {
|
||||||
|
/*
|
||||||
|
duplicates are generated when multiple bookmarks correspond to the same page,
|
||||||
|
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
||||||
|
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
||||||
|
*/
|
||||||
|
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
||||||
|
}
|
||||||
|
for (Bookmark bookmark : bookmarks) {
|
||||||
|
log.info(
|
||||||
|
"{}::::{} to {}",
|
||||||
|
bookmark.getTitle(),
|
||||||
|
bookmark.getStartPage(),
|
||||||
|
bookmark.getEndPage());
|
||||||
|
}
|
||||||
|
List<ByteArrayOutputStream> splitDocumentsBoas =
|
||||||
|
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
||||||
|
|
||||||
|
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
||||||
|
|
||||||
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
sourceDocument.close();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (sourceDocument != null) {
|
||||||
|
sourceDocument.close();
|
||||||
|
}
|
||||||
|
if (zipFile != null) {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error while cleaning up resources", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
||||||
|
String mergedTitle = "";
|
||||||
|
List<Bookmark> chaptersToBeRemoved = new ArrayList<>();
|
||||||
|
for (Bookmark bookmark : bookmarks) {
|
||||||
|
if (bookmark.getStartPage() == bookmark.getEndPage()) {
|
||||||
|
mergedTitle = mergedTitle.concat(bookmark.getTitle().concat(" "));
|
||||||
|
chaptersToBeRemoved.add(bookmark);
|
||||||
|
} else {
|
||||||
|
if (!mergedTitle.isEmpty()) {
|
||||||
|
if (mergedTitle.length() > 255) {
|
||||||
|
mergedTitle = mergedTitle.substring(0, 253) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarks.set(
|
||||||
|
bookmarks.indexOf(bookmark),
|
||||||
|
new Bookmark(
|
||||||
|
mergedTitle, bookmark.getStartPage(), bookmark.getEndPage()));
|
||||||
|
}
|
||||||
|
mergedTitle = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bookmarks.removeAll(chaptersToBeRemoved);
|
||||||
|
return bookmarks;
|
||||||
|
}
|
||||||
|
|
||||||
private Path createZipFile(
|
private Path createZipFile(
|
||||||
List<Bookmark> bookmarks, List<ByteArrayOutputStream> splitDocumentsBoas)
|
List<Bookmark> bookmarks, List<ByteArrayOutputStream> splitDocumentsBoas)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -256,14 +253,14 @@ public class SplitPdfByChaptersController {
|
|||||||
zipOut.write(pdf);
|
zipOut.write(pdf);
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
|
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
log.info("Wrote split document {} to zip file", fileName);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed writing to zip", e);
|
log.error("Failed writing to zip", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile);
|
log.info("Successfully created zip file with split documents: {}", zipFile);
|
||||||
return zipFile;
|
return zipFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +281,7 @@ public class SplitPdfByChaptersController {
|
|||||||
i++) {
|
i++) {
|
||||||
PDPage page = sourceDocument.getPage(i);
|
PDPage page = sourceDocument.getPage(i);
|
||||||
splitDocument.addPage(page);
|
splitDocument.addPage(page);
|
||||||
logger.info("Adding page {} to split document", i);
|
log.info("Adding page {} to split document", i);
|
||||||
}
|
}
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
if (includeMetadata) {
|
if (includeMetadata) {
|
||||||
@@ -295,7 +292,7 @@ public class SplitPdfByChaptersController {
|
|||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
splitDocumentsBoas.add(baos);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
log.error("Failed splitting documents and saving them", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
|||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -42,9 +40,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPdfBySectionsController {
|
public class SplitPdfBySectionsController {
|
||||||
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -25,6 +23,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
@@ -32,10 +31,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPdfBySizeController {
|
public class SplitPdfBySizeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -78,7 +77,7 @@ public class SplitPdfBySizeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
} finally {
|
} finally {
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -32,8 +30,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class ToSinglePageController {
|
public class ToSinglePageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@@ -18,11 +17,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
|||||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
import org.springframework.web.servlet.view.RedirectView;
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
|
||||||
@@ -45,9 +40,14 @@ import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
@Autowired private UserService userService;
|
private static final String LOGIN_MESSAGETYPE_CREDSUPDATED = "/login?messageType=credsUpdated";
|
||||||
|
private final UserService userService;
|
||||||
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
@Autowired SessionPersistentRegistry sessionRegistry;
|
public UserController(UserService userService, SessionPersistentRegistry sessionRegistry) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
@@ -75,36 +75,27 @@ public class UserController {
|
|||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
RedirectAttributes redirectAttributes)
|
RedirectAttributes redirectAttributes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
if (!userService.isUsernameValid(newUsername)) {
|
if (!userService.isUsernameValid(newUsername)) {
|
||||||
return new RedirectView("/account?messageType=invalidUsername", true);
|
return new RedirectView("/account?messageType=invalidUsername", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The username MUST be unique when renaming
|
// The username MUST be unique when renaming
|
||||||
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||||
|
|
||||||
if (userOpt == null || userOpt.isEmpty()) {
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
return new RedirectView("/account?messageType=userNotFound", true);
|
return new RedirectView("/account?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
if (user.getUsername().equals(newUsername)) {
|
if (user.getUsername().equals(newUsername)) {
|
||||||
return new RedirectView("/account?messageType=usernameExists", true);
|
return new RedirectView("/account?messageType=usernameExists", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
return new RedirectView("/account?messageType=incorrectPassword", true);
|
return new RedirectView("/account?messageType=incorrectPassword", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
|
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
|
||||||
return new RedirectView("/account?messageType=usernameExists", true);
|
return new RedirectView("/account?messageType=usernameExists", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newUsername != null && newUsername.length() > 0) {
|
if (newUsername != null && newUsername.length() > 0) {
|
||||||
try {
|
try {
|
||||||
userService.changeUsername(user, newUsername);
|
userService.changeUsername(user, newUsername);
|
||||||
@@ -112,10 +103,8 @@ public class UserController {
|
|||||||
return new RedirectView("/account?messageType=invalidUsername", true);
|
return new RedirectView("/account?messageType=invalidUsername", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout using Spring's utility
|
// Logout using Spring's utility
|
||||||
new SecurityContextLogoutHandler().logout(request, response, null);
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,24 +121,18 @@ public class UserController {
|
|||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
|
|
||||||
if (userOpt == null || userOpt.isEmpty()) {
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
return new RedirectView("/change-creds?messageType=incorrectPassword", true);
|
return new RedirectView("/change-creds?messageType=incorrectPassword", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
userService.changePassword(user, newPassword);
|
userService.changePassword(user, newPassword);
|
||||||
userService.changeFirstUse(user, false);
|
userService.changeFirstUse(user, false);
|
||||||
// Logout using Spring's utility
|
// Logout using Spring's utility
|
||||||
new SecurityContextLogoutHandler().logout(request, response, null);
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,24 +149,17 @@ public class UserController {
|
|||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
|
|
||||||
if (userOpt == null || userOpt.isEmpty()) {
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
return new RedirectView("/account?messageType=userNotFound", true);
|
return new RedirectView("/account?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
return new RedirectView("/account?messageType=incorrectPassword", true);
|
return new RedirectView("/account?messageType=incorrectPassword", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
userService.changePassword(user, newPassword);
|
userService.changePassword(user, newPassword);
|
||||||
|
|
||||||
// Logout using Spring's utility
|
// Logout using Spring's utility
|
||||||
new SecurityContextLogoutHandler().logout(request, response, null);
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,17 +169,14 @@ public class UserController {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
Map<String, String[]> paramMap = request.getParameterMap();
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
Map<String, String> updates = new HashMap<>();
|
Map<String, String> updates = new HashMap<>();
|
||||||
|
|
||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Processed updates: " + updates);
|
log.debug("Processed updates: " + updates);
|
||||||
|
|
||||||
// Assuming you have a method in userService to update the settings for a user
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
userService.updateUserSettings(principal.getName(), updates);
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
|
// Redirect to a page of your choice after updating
|
||||||
return "redirect:/account"; // Redirect to a page of your choice after updating
|
return "redirect:/account";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@@ -216,13 +189,10 @@ public class UserController {
|
|||||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||||
boolean forceChange)
|
boolean forceChange)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, IOException {
|
||||||
|
|
||||||
if (!userService.isUsernameValid(username)) {
|
if (!userService.isUsernameValid(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user != null && user.getUsername().equalsIgnoreCase(username)) {
|
if (user != null && user.getUsername().equalsIgnoreCase(username)) {
|
||||||
@@ -243,7 +213,6 @@ public class UserController {
|
|||||||
// If the role ID is not valid, redirect with an error message
|
// If the role ID is not valid, redirect with an error message
|
||||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
|
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
|
||||||
userService.saveUser(username, AuthenticationType.SSO, role);
|
userService.saveUser(username, AuthenticationType.SSO, role);
|
||||||
} else {
|
} else {
|
||||||
@@ -252,9 +221,9 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
userService.saveUser(username, password, role, forceChange);
|
userService.saveUser(username, password, role, forceChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RedirectView(
|
return new RedirectView(
|
||||||
"/addUsers", true); // Redirect to account page after adding the user
|
"/addUsers", // Redirect to account page after adding the user
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@@ -264,9 +233,7 @@ public class UserController {
|
|||||||
@RequestParam(name = "role") String role,
|
@RequestParam(name = "role") String role,
|
||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
if (!userOpt.isPresent()) {
|
if (!userOpt.isPresent()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
@@ -275,7 +242,6 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
// Get the currently authenticated username
|
// Get the currently authenticated username
|
||||||
String currentUsername = authentication.getName();
|
String currentUsername = authentication.getName();
|
||||||
|
|
||||||
// Check if the provided username matches the current session's username
|
// Check if the provided username matches the current session's username
|
||||||
if (currentUsername.equalsIgnoreCase(username)) {
|
if (currentUsername.equalsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=downgradeCurrentUser", true);
|
return new RedirectView("/addUsers?messageType=downgradeCurrentUser", true);
|
||||||
@@ -292,11 +258,10 @@ public class UserController {
|
|||||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
userService.changeRole(user, role);
|
userService.changeRole(user, role);
|
||||||
|
|
||||||
return new RedirectView(
|
return new RedirectView(
|
||||||
"/addUsers", true); // Redirect to account page after adding the user
|
"/addUsers", // Redirect to account page after adding the user
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@@ -306,9 +271,7 @@ public class UserController {
|
|||||||
@RequestParam("enabled") boolean enabled,
|
@RequestParam("enabled") boolean enabled,
|
||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
if (!userOpt.isPresent()) {
|
if (!userOpt.isPresent()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
@@ -317,15 +280,12 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
// Get the currently authenticated username
|
// Get the currently authenticated username
|
||||||
String currentUsername = authentication.getName();
|
String currentUsername = authentication.getName();
|
||||||
|
|
||||||
// Check if the provided username matches the current session's username
|
// Check if the provided username matches the current session's username
|
||||||
if (currentUsername.equalsIgnoreCase(username)) {
|
if (currentUsername.equalsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=disabledCurrentUser", true);
|
return new RedirectView("/addUsers?messageType=disabledCurrentUser", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
userService.changeUserEnabled(user, enabled);
|
userService.changeUserEnabled(user, enabled);
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
// Invalidate all sessions if the user is being disabled
|
// Invalidate all sessions if the user is being disabled
|
||||||
List<Object> principals = sessionRegistry.getAllPrincipals();
|
List<Object> principals = sessionRegistry.getAllPrincipals();
|
||||||
@@ -349,28 +309,24 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RedirectView(
|
return new RedirectView(
|
||||||
"/addUsers", true); // Redirect to account page after adding the user
|
"/addUsers", // Redirect to account page after adding the user
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@PostMapping("/admin/deleteUser/{username}")
|
@PostMapping("/admin/deleteUser/{username}")
|
||||||
public RedirectView deleteUser(
|
public RedirectView deleteUser(
|
||||||
@PathVariable("username") String username, Authentication authentication) {
|
@PathVariable("username") String username, Authentication authentication) {
|
||||||
|
|
||||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
|
return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the currently authenticated username
|
// Get the currently authenticated username
|
||||||
String currentUsername = authentication.getName();
|
String currentUsername = authentication.getName();
|
||||||
|
|
||||||
// Check if the provided username matches the current session's username
|
// Check if the provided username matches the current session's username
|
||||||
if (currentUsername.equalsIgnoreCase(username)) {
|
if (currentUsername.equalsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
|
return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate all sessions before deleting the user
|
// Invalidate all sessions before deleting the user
|
||||||
List<SessionInformation> sessionsInformations =
|
List<SessionInformation> sessionsInformations =
|
||||||
sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
|
sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
|
||||||
@@ -410,6 +366,4 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
return ResponseEntity.ok(apiKey);
|
return ResponseEntity.ok(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String LOGIN_MESSAGETYPE_CREDSUPDATED = "/login?messageType=credsUpdated";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -29,6 +27,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
@@ -40,11 +39,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertImgPDFController {
|
public class ConvertImgPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -95,7 +93,7 @@ public class ConvertImgPDFController {
|
|||||||
Integer.valueOf(dpi),
|
Integer.valueOf(dpi),
|
||||||
filename);
|
filename);
|
||||||
if (result == null || result.length == 0) {
|
if (result == null || result.length == 0) {
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
log.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
}
|
}
|
||||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
||||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||||
@@ -142,7 +140,7 @@ public class ConvertImgPDFController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (webpFiles.isEmpty()) {
|
if (webpFiles.isEmpty()) {
|
||||||
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
log.error("No WebP files were created in: {}", tempOutputDir.toString());
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"No WebP files were created. " + resultProcess.getMessages());
|
"No WebP files were created. " + resultProcess.getMessages());
|
||||||
}
|
}
|
||||||
@@ -194,7 +192,7 @@ public class ConvertImgPDFController {
|
|||||||
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error cleaning up temporary files", e);
|
log.error("Error cleaning up temporary files", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +208,13 @@ public class ConvertImgPDFController {
|
|||||||
String fitOption = request.getFitOption();
|
String fitOption = request.getFitOption();
|
||||||
String colorType = request.getColorType();
|
String colorType = request.getColorType();
|
||||||
boolean autoRotate = request.isAutoRotate();
|
boolean autoRotate = request.isAutoRotate();
|
||||||
|
// Handle Null entries for formdata
|
||||||
|
if (colorType == null || colorType.isBlank()) {
|
||||||
|
colorType = "color";
|
||||||
|
}
|
||||||
|
if (fitOption == null || fitOption.isEmpty()) {
|
||||||
|
fitOption = "fillPage";
|
||||||
|
}
|
||||||
// Convert the file to PDF and get the resulting bytes
|
// Convert the file to PDF and get the resulting bytes
|
||||||
byte[] bytes =
|
byte[] bytes =
|
||||||
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertOfficeController {
|
public class ConvertOfficeController {
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
// Check for valid file extension
|
// Check for valid file extension
|
||||||
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||||
@@ -78,13 +85,6 @@ public class ConvertOfficeController {
|
|||||||
return fileExtension.matches(extensionPattern);
|
return fileExtension.matches(extensionPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using LibreOffice",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -26,9 +25,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
// @RequestMapping("/api/v1/convert")
|
// @RequestMapping("/api/v1/convert")
|
||||||
public class ConvertPDFToBookController {
|
public class ConvertPDFToBookController {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean bookAndHtmlFormatsInstalled;
|
private final boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
|
public ConvertPDFToBookController(
|
||||||
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -39,16 +42,13 @@ public class ConvertPDFToBookController {
|
|||||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (!bookAndHtmlFormatsInstalled) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
|
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
throw new IllegalArgumentException("Please provide a file for conversion.");
|
throw new IllegalArgumentException("Please provide a file for conversion.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the output format
|
// Validate the output format
|
||||||
String outputFormat = request.getOutputFormat().toLowerCase();
|
String outputFormat = request.getOutputFormat().toLowerCase();
|
||||||
List<String> allowedFormats =
|
List<String> allowedFormats =
|
||||||
@@ -58,28 +58,24 @@ public class ConvertPDFToBookController {
|
|||||||
if (!allowedFormats.contains(outputFormat)) {
|
if (!allowedFormats.contains(outputFormat)) {
|
||||||
throw new IllegalArgumentException("Invalid output format: " + outputFormat);
|
throw new IllegalArgumentException("Invalid output format: " + outputFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] outputFileBytes;
|
byte[] outputFileBytes;
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
Path tempOutputFile =
|
Path tempOutputFile =
|
||||||
Files.createTempFile(
|
Files.createTempFile(
|
||||||
"output_",
|
"output_", // Use the output format for the file extension
|
||||||
"." + outputFormat); // Use the output format for the file extension
|
"." + outputFormat);
|
||||||
Path tempInputFile = null;
|
Path tempInputFile = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create temp input file from the provided PDF
|
// Create temp input file from the provided PDF
|
||||||
tempInputFile = Files.createTempFile("input_", ".pdf"); // Assuming input is always PDF
|
// Assuming input is always PDF
|
||||||
|
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
Files.write(tempInputFile, fileInput.getBytes());
|
Files.write(tempInputFile, fileInput.getBytes());
|
||||||
|
|
||||||
command.add("ebook-convert");
|
command.add("ebook-convert");
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
outputFileBytes = Files.readAllBytes(tempOutputFile);
|
outputFileBytes = Files.readAllBytes(tempOutputFile);
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up temporary files
|
// Clean up temporary files
|
||||||
@@ -88,13 +84,12 @@ public class ConvertPDFToBookController {
|
|||||||
}
|
}
|
||||||
Files.deleteIfExists(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "."
|
+ "."
|
||||||
+ outputFormat; // Remove file extension and append .pdf
|
+ // Remove file extension and append .pdf
|
||||||
|
outputFormat;
|
||||||
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
|
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
@@ -29,11 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToPDFA {
|
public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
@@ -46,7 +44,7 @@ public class ConvertPDFToPDFA {
|
|||||||
|
|
||||||
// Validate input file type
|
// Validate input file type
|
||||||
if (!"application/pdf".equals(inputFile.getContentType())) {
|
if (!"application/pdf".equals(inputFile.getContentType())) {
|
||||||
logger.error("Invalid input file type: {}", inputFile.getContentType());
|
log.error("Invalid input file type: {}", inputFile.getContentType());
|
||||||
throw new IllegalArgumentException("Input file must be a PDF");
|
throw new IllegalArgumentException("Input file must be a PDF");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +94,7 @@ public class ConvertPDFToPDFA {
|
|||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
if (returnCode.getRc() != 0) {
|
if (returnCode.getRc() != 0) {
|
||||||
logger.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
|
log.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
|
||||||
throw new RuntimeException("PDF/A conversion failed");
|
throw new RuntimeException("PDF/A conversion failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -19,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
@@ -28,11 +27,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@Slf4j
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertWebsiteToPDF {
|
public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -88,7 +86,7 @@ public class ConvertWebsiteToPDF {
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error deleting temporary output file", e);
|
log.error("Error deleting temporary output file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import org.apache.commons.csv.CSVFormat;
|
|||||||
import org.apache.commons.csv.QuoteMode;
|
import org.apache.commons.csv.QuoteMode;
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.ContentDisposition;
|
import org.springframework.http.ContentDisposition;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -34,8 +32,6 @@ import technology.tabula.writers.Writer;
|
|||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ExtractCSVController {
|
public class ExtractCSVController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
|
|
||||||
|
|
||||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extracts a CSV document from a PDF",
|
summary = "Extracts a CSV document from a PDF",
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import org.apache.pdfbox.Loader;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.apache.pdfbox.text.TextPosition;
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -22,16 +20,16 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoRenameController {
|
public class AutoRenameController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class);
|
|
||||||
|
|
||||||
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
|
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
|
||||||
private static final int LINE_LIMIT = 200;
|
private static final int LINE_LIMIT = 200;
|
||||||
|
|
||||||
@@ -48,16 +46,6 @@ public class AutoRenameController {
|
|||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
PDFTextStripper reader =
|
PDFTextStripper reader =
|
||||||
new PDFTextStripper() {
|
new PDFTextStripper() {
|
||||||
class LineInfo {
|
|
||||||
String text;
|
|
||||||
float fontSize;
|
|
||||||
|
|
||||||
LineInfo(String text, float fontSize) {
|
|
||||||
this.text = text;
|
|
||||||
this.fontSize = fontSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<LineInfo> lineInfos = new ArrayList<>();
|
List<LineInfo> lineInfos = new ArrayList<>();
|
||||||
StringBuilder lineBuilder = new StringBuilder();
|
StringBuilder lineBuilder = new StringBuilder();
|
||||||
float lastY = -1;
|
float lastY = -1;
|
||||||
@@ -124,6 +112,16 @@ public class AutoRenameController {
|
|||||||
.text)
|
.text)
|
||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LineInfo {
|
||||||
|
String text;
|
||||||
|
float fontSize;
|
||||||
|
|
||||||
|
LineInfo(String text, float fontSize) {
|
||||||
|
this.text = text;
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
String header = reader.getText(document);
|
String header = reader.getText(document);
|
||||||
@@ -133,7 +131,7 @@ public class AutoRenameController {
|
|||||||
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
|
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
||||||
} else {
|
} else {
|
||||||
logger.info("File has no good title to be found");
|
log.info("File has no good title to be found");
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -25,28 +23,24 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.*;
|
||||||
import com.google.zxing.LuminanceSource;
|
|
||||||
import com.google.zxing.MultiFormatReader;
|
|
||||||
import com.google.zxing.NotFoundException;
|
|
||||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
|
||||||
import com.google.zxing.Result;
|
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoSplitPdfController {
|
public class AutoSplitPdfController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AutoSplitPdfController.class);
|
|
||||||
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
||||||
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
|
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
|
||||||
|
|
||||||
@@ -57,6 +51,52 @@ public class AutoSplitPdfController {
|
|||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String decodeQRCode(BufferedImage bufferedImage) {
|
||||||
|
LuminanceSource source;
|
||||||
|
|
||||||
|
if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) {
|
||||||
|
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
|
||||||
|
source =
|
||||||
|
new PlanarYUVLuminanceSource(
|
||||||
|
pixels,
|
||||||
|
bufferedImage.getWidth(),
|
||||||
|
bufferedImage.getHeight(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
bufferedImage.getWidth(),
|
||||||
|
bufferedImage.getHeight(),
|
||||||
|
false);
|
||||||
|
} else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
||||||
|
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
||||||
|
byte[] newPixels = new byte[pixels.length];
|
||||||
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
|
newPixels[i] = (byte) (pixels[i] & 0xff);
|
||||||
|
}
|
||||||
|
source =
|
||||||
|
new PlanarYUVLuminanceSource(
|
||||||
|
newPixels,
|
||||||
|
bufferedImage.getWidth(),
|
||||||
|
bufferedImage.getHeight(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
bufferedImage.getWidth(),
|
||||||
|
bufferedImage.getHeight(),
|
||||||
|
false);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data");
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Result result = new MultiFormatReader().decode(bitmap);
|
||||||
|
return result.getText();
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
return null; // there is no QR code in the image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Auto split PDF pages into separate documents",
|
summary = "Auto split PDF pages into separate documents",
|
||||||
@@ -134,7 +174,7 @@ public class AutoSplitPdfController {
|
|||||||
try {
|
try {
|
||||||
document.close();
|
document.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error closing main PDDocument", e);
|
log.error("Error closing main PDDocument", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +182,7 @@ public class AutoSplitPdfController {
|
|||||||
try {
|
try {
|
||||||
splitDoc.close();
|
splitDoc.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error closing split PDDocument", e);
|
log.error("Error closing split PDDocument", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,55 +190,9 @@ public class AutoSplitPdfController {
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error deleting temporary zip file", e);
|
log.error("Error deleting temporary zip file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String decodeQRCode(BufferedImage bufferedImage) {
|
|
||||||
LuminanceSource source;
|
|
||||||
|
|
||||||
if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) {
|
|
||||||
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
|
|
||||||
source =
|
|
||||||
new PlanarYUVLuminanceSource(
|
|
||||||
pixels,
|
|
||||||
bufferedImage.getWidth(),
|
|
||||||
bufferedImage.getHeight(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bufferedImage.getWidth(),
|
|
||||||
bufferedImage.getHeight(),
|
|
||||||
false);
|
|
||||||
} else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
|
||||||
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
|
||||||
byte[] newPixels = new byte[pixels.length];
|
|
||||||
for (int i = 0; i < pixels.length; i++) {
|
|
||||||
newPixels[i] = (byte) (pixels[i] & 0xff);
|
|
||||||
}
|
|
||||||
source =
|
|
||||||
new PlanarYUVLuminanceSource(
|
|
||||||
newPixels,
|
|
||||||
bufferedImage.getWidth(),
|
|
||||||
bufferedImage.getHeight(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bufferedImage.getWidth(),
|
|
||||||
bufferedImage.getHeight(),
|
|
||||||
false);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data");
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
|
||||||
|
|
||||||
try {
|
|
||||||
Result result = new MultiFormatReader().decode(bitmap);
|
|
||||||
return result.getText();
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
return null; // there is no QR code in the image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -30,6 +28,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
@@ -37,11 +36,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class BlankPageController {
|
public class BlankPageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -49,6 +47,32 @@ public class BlankPageController {
|
|||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBlankImage(
|
||||||
|
BufferedImage image, int threshold, double whitePercent, int blurSize) {
|
||||||
|
if (image == null) {
|
||||||
|
log.info("Error: Image is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to binary image based on the threshold
|
||||||
|
int whitePixels = 0;
|
||||||
|
int totalPixels = image.getWidth() * image.getHeight();
|
||||||
|
|
||||||
|
for (int i = 0; i < image.getHeight(); i++) {
|
||||||
|
for (int j = 0; j < image.getWidth(); j++) {
|
||||||
|
int color = image.getRGB(j, i) & 0xFF;
|
||||||
|
if (color >= 255 - threshold) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
|
||||||
|
log.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
|
||||||
|
|
||||||
|
return whitePixelPercentage >= whitePercent;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
@@ -71,7 +95,7 @@ public class BlankPageController {
|
|||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
pdfRenderer.setSubsamplingAllowed(true);
|
pdfRenderer.setSubsamplingAllowed(true);
|
||||||
for (PDPage page : pages) {
|
for (PDPage page : pages) {
|
||||||
logger.info("checking page {}", pageIndex);
|
log.info("checking page {}", pageIndex);
|
||||||
textStripper.setStartPage(pageIndex + 1);
|
textStripper.setStartPage(pageIndex + 1);
|
||||||
textStripper.setEndPage(pageIndex + 1);
|
textStripper.setEndPage(pageIndex + 1);
|
||||||
String pageText = textStripper.getText(document);
|
String pageText = textStripper.getText(document);
|
||||||
@@ -79,12 +103,12 @@ public class BlankPageController {
|
|||||||
|
|
||||||
boolean blank = true;
|
boolean blank = true;
|
||||||
if (hasText) {
|
if (hasText) {
|
||||||
logger.info("page {} has text, not blank", pageIndex);
|
log.info("page {} has text, not blank", pageIndex);
|
||||||
blank = false;
|
blank = false;
|
||||||
} else {
|
} else {
|
||||||
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
||||||
if (hasImages) {
|
if (hasImages) {
|
||||||
logger.info("page {} has image, running blank detection", pageIndex);
|
log.info("page {} has image, running blank detection", pageIndex);
|
||||||
// Render image and save as temp file
|
// Render image and save as temp file
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
|
||||||
blank = isBlankImage(image, threshold, whitePercent, threshold);
|
blank = isBlankImage(image, threshold, whitePercent, threshold);
|
||||||
@@ -92,10 +116,10 @@ public class BlankPageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blank) {
|
if (blank) {
|
||||||
logger.info("Skipping, Image was blank for page #{}", pageIndex);
|
log.info("Skipping, Image was blank for page #{}", pageIndex);
|
||||||
blankPages.add(page);
|
blankPages.add(page);
|
||||||
} else {
|
} else {
|
||||||
logger.info("page {} has image which is not blank", pageIndex);
|
log.info("page {} has image which is not blank", pageIndex);
|
||||||
nonBlankPages.add(page);
|
nonBlankPages.add(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,12 +145,12 @@ public class BlankPageController {
|
|||||||
|
|
||||||
zos.close();
|
zos.close();
|
||||||
|
|
||||||
logger.info("Returning ZIP file: {}", filename + "_processed.zip");
|
log.info("Returning ZIP file: {}", filename + "_processed.zip");
|
||||||
return WebResponseUtils.boasToWebResponse(
|
return WebResponseUtils.boasToWebResponse(
|
||||||
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
|
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,30 +169,4 @@ public class BlankPageController {
|
|||||||
zos.closeEntry();
|
zos.closeEntry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBlankImage(
|
|
||||||
BufferedImage image, int threshold, double whitePercent, int blurSize) {
|
|
||||||
if (image == null) {
|
|
||||||
logger.info("Error: Image is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to binary image based on the threshold
|
|
||||||
int whitePixels = 0;
|
|
||||||
int totalPixels = image.getWidth() * image.getHeight();
|
|
||||||
|
|
||||||
for (int i = 0; i < image.getHeight(); i++) {
|
|
||||||
for (int j = 0; j < image.getWidth(); j++) {
|
|
||||||
int color = image.getRGB(j, i) & 0xFF;
|
|
||||||
if (color >= 255 - threshold) {
|
|
||||||
whitePixels++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
|
|
||||||
logger.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
|
|
||||||
|
|
||||||
return whitePixelPercentage >= whitePercent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.Image;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -17,8 +17,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
|||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -31,6 +29,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
@@ -40,11 +39,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class CompressController {
|
public class CompressController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -191,7 +189,7 @@ public class CompressController {
|
|||||||
incrementOptimizeLevel(
|
incrementOptimizeLevel(
|
||||||
optimizeLevel, outputFileSize, expectedOutputSize);
|
optimizeLevel, outputFileSize, expectedOutputSize);
|
||||||
if (autoMode && optimizeLevel > 9) {
|
if (autoMode && optimizeLevel > 9) {
|
||||||
logger.info("Maximum compression level reached in auto mode");
|
log.info("Maximum compression level reached in auto mode");
|
||||||
sizeMet = true;
|
sizeMet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +201,7 @@ public class CompressController {
|
|||||||
|
|
||||||
// Check if optimized file is larger than the original
|
// Check if optimized file is larger than the original
|
||||||
if (pdfBytes.length > inputFileSize) {
|
if (pdfBytes.length > inputFileSize) {
|
||||||
logger.warn(
|
log.warn(
|
||||||
"Optimized file is larger than the original. Returning the original file instead.");
|
"Optimized file is larger than the original. Returning the original file instead.");
|
||||||
finalFile = tempInputFile;
|
finalFile = tempInputFile;
|
||||||
}
|
}
|
||||||
@@ -234,7 +232,7 @@ public class CompressController {
|
|||||||
|
|
||||||
private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
|
private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
|
||||||
double currentRatio = currentSize / (double) targetSize;
|
double currentRatio = currentSize / (double) targetSize;
|
||||||
logger.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
|
log.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
|
||||||
|
|
||||||
if (currentRatio > 2.0) {
|
if (currentRatio > 2.0) {
|
||||||
return Math.min(9, currentLevel + 3);
|
return Math.min(9, currentLevel + 3);
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import org.apache.commons.io.FileUtils;
|
|||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -31,6 +29,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
|
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
|
||||||
import stirling.software.SPDF.utils.CheckProgramInstall;
|
import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
@@ -39,10 +38,11 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class ExtractImageScansController {
|
public class ExtractImageScansController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
|
private static final String REPLACEFIRST = "[.][^.]+$";
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -201,7 +201,7 @@ public class ExtractImageScansController {
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(path);
|
Files.deleteIfExists(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to delete temporary image file: " + path, e);
|
log.error("Failed to delete temporary image file: " + path, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ public class ExtractImageScansController {
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(tempZipFile);
|
Files.deleteIfExists(tempZipFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
log.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,11 +218,9 @@ public class ExtractImageScansController {
|
|||||||
try {
|
try {
|
||||||
FileUtils.deleteDirectory(dir.toFile());
|
FileUtils.deleteDirectory(dir.toFile());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to delete temporary directory: " + dir, e);
|
log.error("Failed to delete temporary directory: " + dir, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String REPLACEFIRST = "[.][^.]+$";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import org.apache.pdfbox.cos.COSName;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -39,17 +37,17 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
|
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
|
||||||
import stirling.software.SPDF.utils.ImageProcessingUtils;
|
import stirling.software.SPDF.utils.ImageProcessingUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class ExtractImagesController {
|
public class ExtractImagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extract images from a PDF file",
|
summary = "Extract images from a PDF file",
|
||||||
@@ -107,7 +105,7 @@ public class ExtractImagesController {
|
|||||||
allowDuplicates);
|
allowDuplicates);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Log the error and continue processing other pages
|
// Log the error and continue processing other pages
|
||||||
logger.error(
|
log.error(
|
||||||
"Error extracting images from page {}: {}",
|
"Error extracting images from page {}: {}",
|
||||||
pageNum,
|
pageNum,
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
@@ -167,7 +165,7 @@ public class ExtractImagesController {
|
|||||||
try {
|
try {
|
||||||
md = MessageDigest.getInstance("MD5");
|
md = MessageDigest.getInstance("MD5");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
logger.error("MD5 algorithm not available for extractImages hash.", e);
|
log.error("MD5 algorithm not available for extractImages hash.", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (page.getResources() == null || page.getResources().getXObjectNames() == null) {
|
if (page.getResources() == null || page.getResources().getXObjectNames() == null) {
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.AlphaComposite;
|
import java.awt.*;
|
||||||
import java.awt.BasicStroke;
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.GradientPaint;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Ellipse2D;
|
import java.awt.geom.Ellipse2D;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.image.AffineTransformOp;
|
import java.awt.image.*;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.BufferedImageOp;
|
|
||||||
import java.awt.image.ConvolveOp;
|
|
||||||
import java.awt.image.Kernel;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -27,8 +18,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -50,8 +39,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class FakeScanControllerWIP {
|
public class FakeScanControllerWIP {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
|
||||||
|
|
||||||
// TODO finish
|
// TODO finish
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/fake-scan")
|
@PostMapping(consumes = "multipart/form-data", value = "/fake-scan")
|
||||||
@Hidden
|
@Hidden
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -26,17 +24,17 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class FlattenController {
|
public class FlattenController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -84,7 +82,7 @@ public class FlattenController {
|
|||||||
contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);
|
contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
|
|||||||
@@ -11,32 +11,26 @@ import org.apache.pdfbox.Loader;
|
|||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.bind.annotation.InitBinder;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
|
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class MetadataController {
|
public class MetadataController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MetadataController.class);
|
|
||||||
|
|
||||||
private String checkUndefined(String entry) {
|
private String checkUndefined(String entry) {
|
||||||
// Check if the string is "undefined"
|
// Check if the string is "undefined"
|
||||||
if ("undefined".equals(entry)) {
|
if ("undefined".equals(entry)) {
|
||||||
@@ -148,7 +142,7 @@ public class MetadataController {
|
|||||||
creationDateCal.setTime(
|
creationDateCal.setTime(
|
||||||
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
|
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
info.setCreationDate(creationDateCal);
|
info.setCreationDate(creationDateCal);
|
||||||
} else {
|
} else {
|
||||||
@@ -160,7 +154,7 @@ public class MetadataController {
|
|||||||
modificationDateCal.setTime(
|
modificationDateCal.setTime(
|
||||||
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
|
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
info.setModificationDate(modificationDateCal);
|
info.setModificationDate(modificationDateCal);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
@@ -24,7 +16,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -48,12 +39,14 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class OCRController {
|
public class OCRController {
|
||||||
|
|
||||||
@Autowired private ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
public OCRController(
|
||||||
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
|
ApplicationProperties applicationProperties,
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,13 +71,11 @@ public class OCRController {
|
|||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
List<String> languages = request.getLanguages();
|
List<String> languages = request.getLanguages();
|
||||||
String ocrType = request.getOcrType();
|
String ocrType = request.getOcrType();
|
||||||
|
|
||||||
Path tempDir = Files.createTempDirectory("ocr_process");
|
Path tempDir = Files.createTempDirectory("ocr_process");
|
||||||
Path tempInputFile = tempDir.resolve("input.pdf");
|
Path tempInputFile = tempDir.resolve("input.pdf");
|
||||||
Path tempOutputDir = tempDir.resolve("output");
|
Path tempOutputDir = tempDir.resolve("output");
|
||||||
Path tempImagesDir = tempDir.resolve("images");
|
Path tempImagesDir = tempDir.resolve("images");
|
||||||
Path finalOutputFile = tempDir.resolve("final_output.pdf");
|
Path finalOutputFile = tempDir.resolve("final_output.pdf");
|
||||||
|
|
||||||
Files.createDirectories(tempOutputDir);
|
Files.createDirectories(tempOutputDir);
|
||||||
Files.createDirectories(tempImagesDir);
|
Files.createDirectories(tempImagesDir);
|
||||||
Process process = null;
|
Process process = null;
|
||||||
@@ -93,39 +84,32 @@ public class OCRController {
|
|||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
PDFMergerUtility merger = new PDFMergerUtility();
|
PDFMergerUtility merger = new PDFMergerUtility();
|
||||||
merger.setDestinationFileName(finalOutputFile.toString());
|
merger.setDestinationFileName(finalOutputFile.toString());
|
||||||
|
|
||||||
try (PDDocument document = pdfDocumentFactory.load(tempInputFile.toFile())) {
|
try (PDDocument document = pdfDocumentFactory.load(tempInputFile.toFile())) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
int pageCount = document.getNumberOfPages();
|
int pageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
for (int pageNum = 0; pageNum < pageCount; pageNum++) {
|
for (int pageNum = 0; pageNum < pageCount; pageNum++) {
|
||||||
PDPage page = document.getPage(pageNum);
|
PDPage page = document.getPage(pageNum);
|
||||||
boolean hasText = false;
|
boolean hasText = false;
|
||||||
|
|
||||||
// Check for existing text
|
// Check for existing text
|
||||||
try (PDDocument tempDoc = new PDDocument()) {
|
try (PDDocument tempDoc = new PDDocument()) {
|
||||||
tempDoc.addPage(page);
|
tempDoc.addPage(page);
|
||||||
PDFTextStripper stripper = new PDFTextStripper();
|
PDFTextStripper stripper = new PDFTextStripper();
|
||||||
hasText = !stripper.getText(tempDoc).trim().isEmpty();
|
hasText = !stripper.getText(tempDoc).trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldOcr =
|
boolean shouldOcr =
|
||||||
switch (ocrType) {
|
switch (ocrType) {
|
||||||
case "skip-text" -> !hasText;
|
case "skip-text" -> !hasText;
|
||||||
case "force-ocr" -> true;
|
case "force-ocr" -> true;
|
||||||
default -> true;
|
default -> true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Path pageOutputPath =
|
Path pageOutputPath =
|
||||||
tempOutputDir.resolve(String.format("page_%d.pdf", pageNum));
|
tempOutputDir.resolve(String.format("page_%d.pdf", pageNum));
|
||||||
|
|
||||||
if (shouldOcr) {
|
if (shouldOcr) {
|
||||||
// Convert page to image
|
// Convert page to image
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);
|
||||||
Path imagePath =
|
Path imagePath =
|
||||||
tempImagesDir.resolve(String.format("page_%d.png", pageNum));
|
tempImagesDir.resolve(String.format("page_%d.png", pageNum));
|
||||||
ImageIO.write(image, "png", imagePath.toFile());
|
ImageIO.write(image, "png", imagePath.toFile());
|
||||||
|
|
||||||
// Build OCR command
|
// Build OCR command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("tesseract");
|
command.add("tesseract");
|
||||||
@@ -136,11 +120,10 @@ public class OCRController {
|
|||||||
.toString());
|
.toString());
|
||||||
command.add("-l");
|
command.add("-l");
|
||||||
command.add(String.join("+", languages));
|
command.add(String.join("+", languages));
|
||||||
command.add("pdf"); // Always output PDF
|
// Always output PDF
|
||||||
|
command.add("pdf");
|
||||||
ProcessBuilder pb = new ProcessBuilder(command);
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
process = pb.start();
|
process = pb.start();
|
||||||
|
|
||||||
// Capture any error output
|
// Capture any error output
|
||||||
try (BufferedReader reader =
|
try (BufferedReader reader =
|
||||||
new BufferedReader(
|
new BufferedReader(
|
||||||
@@ -150,13 +133,11 @@ public class OCRController {
|
|||||||
log.debug("Tesseract: {}", line);
|
log.debug("Tesseract: {}", line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Tesseract failed with exit code: " + exitCode);
|
"Tesseract failed with exit code: " + exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add OCR'd PDF to merger
|
// Add OCR'd PDF to merger
|
||||||
merger.addSource(pageOutputPath.toFile());
|
merger.addSource(pageOutputPath.toFile());
|
||||||
} else {
|
} else {
|
||||||
@@ -169,29 +150,24 @@ public class OCRController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge all pages into final PDF
|
// Merge all pages into final PDF
|
||||||
merger.mergeDocuments(null);
|
merger.mergeDocuments(null);
|
||||||
|
|
||||||
// Read the final PDF file
|
// Read the final PDF file
|
||||||
byte[] pdfContent = Files.readAllBytes(finalOutputFile);
|
byte[] pdfContent = Files.readAllBytes(finalOutputFile);
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_OCR.pdf";
|
+ "_OCR.pdf";
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(
|
.header(
|
||||||
"Content-Disposition",
|
"Content-Disposition",
|
||||||
"attachment; filename=\"" + outputFilename + "\"")
|
"attachment; filename=\"" + outputFilename + "\"")
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
.contentType(MediaType.APPLICATION_PDF)
|
||||||
.body(pdfContent);
|
.body(pdfContent);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (process != null) {
|
if (process != null) {
|
||||||
process.destroy();
|
process.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up temporary files
|
// Clean up temporary files
|
||||||
deleteDirectory(tempDir);
|
deleteDirectory(tempDir);
|
||||||
}
|
}
|
||||||
@@ -203,17 +179,14 @@ public class OCRController {
|
|||||||
log.warn("File {} does not exist, skipping", file);
|
log.warn("File {} does not exist, skipping", file);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FileInputStream fis = new FileInputStream(file)) {
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
ZipEntry zipEntry = new ZipEntry(filename);
|
ZipEntry zipEntry = new ZipEntry(filename);
|
||||||
zipOut.putNextEntry(zipEntry);
|
zipOut.putNextEntry(zipEntry);
|
||||||
|
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int length;
|
int length;
|
||||||
while ((length = fis.read(buffer)) >= 0) {
|
while ((length = fis.read(buffer)) >= 0) {
|
||||||
zipOut.write(buffer, 0, length);
|
zipOut.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package stirling.software.SPDF.controller.api.misc;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -17,6 +15,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
@@ -24,11 +23,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class OverlayImageController {
|
public class OverlayImageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -60,7 +58,7 @@ public class OverlayImageController {
|
|||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_overlayed.pdf");
|
+ "_overlayed.pdf");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to add image to PDF", e);
|
log.error("Failed to add image to PDF", e);
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -35,8 +33,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class PageNumbersController {
|
public class PageNumbersController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import java.awt.*;
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.print.PageFormat;
|
import java.awt.print.PageFormat;
|
||||||
import java.awt.print.Printable;
|
import java.awt.print.Printable;
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -31,8 +29,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class RepairController {
|
public class RepairController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import org.apache.pdfbox.Loader;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -29,8 +27,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class ShowJavascript {
|
public class ShowJavascript {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import java.util.Map;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -21,28 +19,35 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
import stirling.software.SPDF.SPdfApplication;
|
||||||
import stirling.software.SPDF.model.ApiEndpoint;
|
import stirling.software.SPDF.model.ApiEndpoint;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class ApiDocService {
|
public class ApiDocService {
|
||||||
|
|
||||||
private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>();
|
private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>();
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ApiDocService.class);
|
private final ServletContext servletContext;
|
||||||
|
private final UserServiceInterface userService;
|
||||||
|
Map<String, List<String>> outputToFileTypes = new HashMap<>();
|
||||||
|
JsonNode apiDocsJsonRootNode;
|
||||||
|
|
||||||
@Autowired private ServletContext servletContext;
|
public ApiDocService(
|
||||||
|
ServletContext servletContext,
|
||||||
|
@Autowired(required = false) UserServiceInterface userService) {
|
||||||
|
this.servletContext = servletContext;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
private String getApiDocsUrl() {
|
private String getApiDocsUrl() {
|
||||||
String contextPath = servletContext.getContextPath();
|
String contextPath = servletContext.getContextPath();
|
||||||
String port = SPdfApplication.getStaticPort();
|
String port = SPdfApplication.getStaticPort();
|
||||||
|
|
||||||
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, List<String>> outputToFileTypes = new HashMap<>();
|
|
||||||
|
|
||||||
public List<String> getExtensionTypes(boolean output, String operationName) {
|
public List<String> getExtensionTypes(boolean output, String operationName) {
|
||||||
if (outputToFileTypes.size() == 0) {
|
if (outputToFileTypes.size() == 0) {
|
||||||
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
|
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
|
||||||
@@ -66,14 +71,12 @@ public class ApiDocService {
|
|||||||
"BOOK", Arrays.asList("epub", "mobi", "azw3", "fb2", "txt", "docx"));
|
"BOOK", Arrays.asList("epub", "mobi", "azw3", "fb2", "txt", "docx"));
|
||||||
// type.
|
// type.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
|
if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
|
||||||
loadApiDocumentation();
|
loadApiDocumentation();
|
||||||
}
|
}
|
||||||
if (!apiDocumentation.containsKey(operationName)) {
|
if (!apiDocumentation.containsKey(operationName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
String description = endpoint.getDescription();
|
String description = endpoint.getDescription();
|
||||||
Pattern pattern = null;
|
Pattern pattern = null;
|
||||||
@@ -92,16 +95,11 @@ public class ApiDocService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
|
||||||
private String getApiKeyForUser() {
|
private String getApiKeyForUser() {
|
||||||
if (userService == null) return "";
|
if (userService == null) return "";
|
||||||
return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());
|
return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode apiDocsJsonRootNode;
|
|
||||||
|
|
||||||
// @EventListener(ApplicationReadyEvent.class)
|
// @EventListener(ApplicationReadyEvent.class)
|
||||||
private synchronized void loadApiDocumentation() {
|
private synchronized void loadApiDocumentation() {
|
||||||
String apiDocsJson = "";
|
String apiDocsJson = "";
|
||||||
@@ -112,15 +110,12 @@ public class ApiDocService {
|
|||||||
headers.set("X-API-KEY", apiKey);
|
headers.set("X-API-KEY", apiKey);
|
||||||
}
|
}
|
||||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
ResponseEntity<String> response =
|
ResponseEntity<String> response =
|
||||||
restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);
|
restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);
|
||||||
apiDocsJson = response.getBody();
|
apiDocsJson = response.getBody();
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
apiDocsJsonRootNode = mapper.readTree(apiDocsJson);
|
apiDocsJsonRootNode = mapper.readTree(apiDocsJson);
|
||||||
|
|
||||||
JsonNode paths = apiDocsJsonRootNode.path("paths");
|
JsonNode paths = apiDocsJsonRootNode.path("paths");
|
||||||
paths.fields()
|
paths.fields()
|
||||||
.forEachRemaining(
|
.forEachRemaining(
|
||||||
@@ -135,7 +130,7 @@ public class ApiDocService {
|
|||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Handle exceptions
|
// Handle exceptions
|
||||||
logger.error("Error grabbing swagger doc, body result {}", apiDocsJson);
|
log.error("Error grabbing swagger doc, body result {}", apiDocsJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,19 +152,15 @@ public class ApiDocService {
|
|||||||
if (!apiDocumentation.containsKey(operationName)) {
|
if (!apiDocumentation.containsKey(operationName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
String description = endpoint.getDescription();
|
String description = endpoint.getDescription();
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("Type:(\\w+)");
|
Pattern pattern = Pattern.compile("Type:(\\w+)");
|
||||||
Matcher matcher = pattern.matcher(description);
|
Matcher matcher = pattern.matcher(description);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String type = matcher.group(1);
|
String type = matcher.group(1);
|
||||||
return type.startsWith("MI");
|
return type.startsWith("MI");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model class for API Endpoint
|
// Model class for API Endpoint
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ import java.util.Map;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
@@ -33,30 +31,39 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/pipeline")
|
@RequestMapping("/api/v1/pipeline")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
||||||
public class PipelineController {
|
public class PipelineController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
|
||||||
|
|
||||||
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||||
|
|
||||||
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||||
@Autowired PipelineProcessor processor;
|
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final PipelineProcessor processor;
|
||||||
|
|
||||||
@Autowired private ObjectMapper objectMapper;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public PipelineController(
|
||||||
|
PipelineProcessor processor,
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
ObjectMapper objectMapper) {
|
||||||
|
this.processor = processor;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/handleData")
|
@PostMapping("/handleData")
|
||||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
||||||
throws JsonMappingException, JsonProcessingException {
|
throws JsonMappingException, JsonProcessingException {
|
||||||
|
|
||||||
MultipartFile[] files = request.getFileInput();
|
MultipartFile[] files = request.getFileInput();
|
||||||
String jsonString = request.getJson();
|
String jsonString = request.getJson();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
logger.info("Received POST request to /handleData with {} files", files.length);
|
log.info("Received POST request to /handleData with {} files", files.length);
|
||||||
try {
|
try {
|
||||||
List<Resource> inputFiles = processor.generateInputFiles(files);
|
List<Resource> inputFiles = processor.generateInputFiles(files);
|
||||||
if (inputFiles == null || inputFiles.size() == 0) {
|
if (inputFiles == null || inputFiles.size() == 0) {
|
||||||
@@ -70,26 +77,21 @@ public class PipelineController {
|
|||||||
byte[] bytes = new byte[(int) singleFile.contentLength()];
|
byte[] bytes = new byte[(int) singleFile.contentLength()];
|
||||||
is.read(bytes);
|
is.read(bytes);
|
||||||
is.close();
|
is.close();
|
||||||
|
log.info("Returning single file response...");
|
||||||
logger.info("Returning single file response...");
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
|
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
|
||||||
} else if (outputFiles == null) {
|
} else if (outputFiles == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a ByteArrayOutputStream to hold the zip
|
// Create a ByteArrayOutputStream to hold the zip
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zipOut = new ZipOutputStream(baos);
|
ZipOutputStream zipOut = new ZipOutputStream(baos);
|
||||||
|
|
||||||
// A map to keep track of filenames and their counts
|
// A map to keep track of filenames and their counts
|
||||||
Map<String, Integer> filenameCount = new HashMap<>();
|
Map<String, Integer> filenameCount = new HashMap<>();
|
||||||
|
|
||||||
// Loop through each file and add it to the zip
|
// Loop through each file and add it to the zip
|
||||||
for (Resource file : outputFiles) {
|
for (Resource file : outputFiles) {
|
||||||
String originalFilename = file.getFilename();
|
String originalFilename = file.getFilename();
|
||||||
String filename = originalFilename;
|
String filename = originalFilename;
|
||||||
|
|
||||||
// Check if the filename already exists, and modify it if necessary
|
// Check if the filename already exists, and modify it if necessary
|
||||||
if (filenameCount.containsKey(originalFilename)) {
|
if (filenameCount.containsKey(originalFilename)) {
|
||||||
int count = filenameCount.get(originalFilename);
|
int count = filenameCount.get(originalFilename);
|
||||||
@@ -100,29 +102,23 @@ public class PipelineController {
|
|||||||
} else {
|
} else {
|
||||||
filenameCount.put(originalFilename, 1);
|
filenameCount.put(originalFilename, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipEntry zipEntry = new ZipEntry(filename);
|
ZipEntry zipEntry = new ZipEntry(filename);
|
||||||
zipOut.putNextEntry(zipEntry);
|
zipOut.putNextEntry(zipEntry);
|
||||||
|
|
||||||
// Read the file into a byte array
|
// Read the file into a byte array
|
||||||
InputStream is = file.getInputStream();
|
InputStream is = file.getInputStream();
|
||||||
byte[] bytes = new byte[(int) file.contentLength()];
|
byte[] bytes = new byte[(int) file.contentLength()];
|
||||||
is.read(bytes);
|
is.read(bytes);
|
||||||
|
|
||||||
// Write the bytes of the file to the zip
|
// Write the bytes of the file to the zip
|
||||||
zipOut.write(bytes, 0, bytes.length);
|
zipOut.write(bytes, 0, bytes.length);
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
|
|
||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
|
log.info("Returning zipped file response...");
|
||||||
logger.info("Returning zipped file response...");
|
|
||||||
return WebResponseUtils.boasToWebResponse(
|
return WebResponseUtils.boasToWebResponse(
|
||||||
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
|
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error handling data: ", e);
|
log.error("Error handling data: ", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -27,27 +24,40 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
import stirling.software.SPDF.utils.FileMonitor;
|
import stirling.software.SPDF.utils.FileMonitor;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class PipelineDirectoryProcessor {
|
public class PipelineDirectoryProcessor {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class);
|
private final ObjectMapper objectMapper;
|
||||||
@Autowired private ObjectMapper objectMapper;
|
|
||||||
@Autowired private ApiDocService apiDocService;
|
|
||||||
@Autowired PipelineProcessor processor;
|
|
||||||
@Autowired FileMonitor fileMonitor;
|
|
||||||
|
|
||||||
final String watchedFoldersDir;
|
private final ApiDocService apiDocService;
|
||||||
final String finishedFoldersDir;
|
|
||||||
|
private final PipelineProcessor processor;
|
||||||
|
|
||||||
|
private final FileMonitor fileMonitor;
|
||||||
|
|
||||||
|
private final String watchedFoldersDir;
|
||||||
|
|
||||||
|
private final String finishedFoldersDir;
|
||||||
|
|
||||||
public PipelineDirectoryProcessor(
|
public PipelineDirectoryProcessor(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
ApiDocService apiDocService,
|
||||||
@Qualifier("watchedFoldersDir") String watchedFoldersDir,
|
@Qualifier("watchedFoldersDir") String watchedFoldersDir,
|
||||||
@Qualifier("finishedFoldersDir") String finishedFoldersDir) {
|
@Qualifier("finishedFoldersDir") String finishedFoldersDir,
|
||||||
|
PipelineProcessor processor,
|
||||||
|
FileMonitor fileMonitor) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.apiDocService = apiDocService;
|
||||||
this.watchedFoldersDir = watchedFoldersDir;
|
this.watchedFoldersDir = watchedFoldersDir;
|
||||||
this.finishedFoldersDir = finishedFoldersDir;
|
this.finishedFoldersDir = finishedFoldersDir;
|
||||||
|
this.processor = processor;
|
||||||
|
this.fileMonitor = fileMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedRate = 60000)
|
@Scheduled(fixedRate = 60000)
|
||||||
@@ -56,9 +66,9 @@ public class PipelineDirectoryProcessor {
|
|||||||
if (!Files.exists(watchedFolderPath)) {
|
if (!Files.exists(watchedFolderPath)) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(watchedFolderPath);
|
Files.createDirectories(watchedFolderPath);
|
||||||
logger.info("Created directory: {}", watchedFolderPath);
|
log.info("Created directory: {}", watchedFolderPath);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error creating directory: {}", watchedFolderPath, e);
|
log.error("Error creating directory: {}", watchedFolderPath, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,24 +81,22 @@ public class PipelineDirectoryProcessor {
|
|||||||
handleDirectory(t);
|
handleDirectory(t);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error handling directory: {}", t, e);
|
log.error("Error handling directory: {}", t, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error walking through directory: {}", watchedFolderPath, e);
|
log.error("Error walking through directory: {}", watchedFolderPath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleDirectory(Path dir) throws IOException {
|
public void handleDirectory(Path dir) throws IOException {
|
||||||
logger.info("Handling directory: {}", dir);
|
log.info("Handling directory: {}", dir);
|
||||||
Path processingDir = createProcessingDirectory(dir);
|
Path processingDir = createProcessingDirectory(dir);
|
||||||
|
|
||||||
Optional<Path> jsonFileOptional = findJsonFile(dir);
|
Optional<Path> jsonFileOptional = findJsonFile(dir);
|
||||||
if (!jsonFileOptional.isPresent()) {
|
if (!jsonFileOptional.isPresent()) {
|
||||||
logger.warn("No .JSON settings file found. No processing will happen for dir {}.", dir);
|
log.warn("No .JSON settings file found. No processing will happen for dir {}.", dir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path jsonFile = jsonFileOptional.get();
|
Path jsonFile = jsonFileOptional.get();
|
||||||
PipelineConfig config = readAndParseJson(jsonFile);
|
PipelineConfig config = readAndParseJson(jsonFile);
|
||||||
processPipelineOperations(dir, processingDir, jsonFile, config);
|
processPipelineOperations(dir, processingDir, jsonFile, config);
|
||||||
@@ -98,7 +106,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
Path processingDir = dir.resolve("processing");
|
Path processingDir = dir.resolve("processing");
|
||||||
if (!Files.exists(processingDir)) {
|
if (!Files.exists(processingDir)) {
|
||||||
Files.createDirectory(processingDir);
|
Files.createDirectory(processingDir);
|
||||||
logger.info("Created processing directory: {}", processingDir);
|
log.info("Created processing directory: {}", processingDir);
|
||||||
}
|
}
|
||||||
return processingDir;
|
return processingDir;
|
||||||
}
|
}
|
||||||
@@ -111,7 +119,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
|
|
||||||
private PipelineConfig readAndParseJson(Path jsonFile) throws IOException {
|
private PipelineConfig readAndParseJson(Path jsonFile) throws IOException {
|
||||||
String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);
|
String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);
|
||||||
logger.debug("Reading JSON file: {}", jsonFile);
|
log.debug("Reading JSON file: {}", jsonFile);
|
||||||
return objectMapper.readValue(jsonString, PipelineConfig.class);
|
return objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +129,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
validateOperation(operation);
|
validateOperation(operation);
|
||||||
File[] files = collectFilesForProcessing(dir, jsonFile, operation);
|
File[] files = collectFilesForProcessing(dir, jsonFile, operation);
|
||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
logger.debug("No files detected for {} ", dir);
|
log.debug("No files detected for {} ", dir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<File> filesToProcess = prepareFilesForProcessing(files, processingDir);
|
List<File> filesToProcess = prepareFilesForProcessing(files, processingDir);
|
||||||
@@ -167,13 +175,11 @@ public class PipelineDirectoryProcessor {
|
|||||||
private Path resolveUniqueFilePath(Path directory, String originalFileName) {
|
private Path resolveUniqueFilePath(Path directory, String originalFileName) {
|
||||||
Path filePath = directory.resolve(originalFileName);
|
Path filePath = directory.resolve(originalFileName);
|
||||||
int counter = 1;
|
int counter = 1;
|
||||||
|
|
||||||
while (Files.exists(filePath)) {
|
while (Files.exists(filePath)) {
|
||||||
String newName = appendSuffixToFileName(originalFileName, "(" + counter + ")");
|
String newName = appendSuffixToFileName(originalFileName, "(" + counter + ")");
|
||||||
filePath = directory.resolve(newName);
|
filePath = directory.resolve(newName);
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +208,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
moveAndRenameFiles(outputFiles, config, dir);
|
moveAndRenameFiles(outputFiles, config, dir);
|
||||||
deleteOriginalFiles(filesToProcess, processingDir);
|
deleteOriginalFiles(filesToProcess, processingDir);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("error during processing", e);
|
log.error("error during processing", e);
|
||||||
moveFilesBack(filesToProcess, processingDir);
|
moveFilesBack(filesToProcess, processingDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,18 +218,15 @@ public class PipelineDirectoryProcessor {
|
|||||||
for (Resource resource : resources) {
|
for (Resource resource : resources) {
|
||||||
String outputFileName = createOutputFileName(resource, config);
|
String outputFileName = createOutputFileName(resource, config);
|
||||||
Path outputPath = determineOutputPath(config, dir);
|
Path outputPath = determineOutputPath(config, dir);
|
||||||
|
|
||||||
if (!Files.exists(outputPath)) {
|
if (!Files.exists(outputPath)) {
|
||||||
Files.createDirectories(outputPath);
|
Files.createDirectories(outputPath);
|
||||||
logger.info("Created directory: {}", outputPath);
|
log.info("Created directory: {}", outputPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path outputFile = outputPath.resolve(outputFileName);
|
Path outputFile = outputPath.resolve(outputFileName);
|
||||||
try (OutputStream os = new FileOutputStream(outputFile.toFile())) {
|
try (OutputStream os = new FileOutputStream(outputFile.toFile())) {
|
||||||
os.write(((ByteArrayResource) resource).getByteArray());
|
os.write(((ByteArrayResource) resource).getByteArray());
|
||||||
}
|
}
|
||||||
|
log.info("File moved and renamed to {}", outputFile);
|
||||||
logger.info("File moved and renamed to {}", outputFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +234,6 @@ public class PipelineDirectoryProcessor {
|
|||||||
String resourceName = resource.getFilename();
|
String resourceName = resource.getFilename();
|
||||||
String baseName = resourceName.substring(0, resourceName.lastIndexOf('.'));
|
String baseName = resourceName.substring(0, resourceName.lastIndexOf('.'));
|
||||||
String extension = resourceName.substring(resourceName.lastIndexOf('.') + 1);
|
String extension = resourceName.substring(resourceName.lastIndexOf('.') + 1);
|
||||||
|
|
||||||
String outputFileName =
|
String outputFileName =
|
||||||
config.getOutputPattern()
|
config.getOutputPattern()
|
||||||
.replace("{filename}", baseName)
|
.replace("{filename}", baseName)
|
||||||
@@ -246,7 +248,6 @@ public class PipelineDirectoryProcessor {
|
|||||||
.format(DateTimeFormatter.ofPattern("HHmmss")))
|
.format(DateTimeFormatter.ofPattern("HHmmss")))
|
||||||
+ "."
|
+ "."
|
||||||
+ extension;
|
+ extension;
|
||||||
|
|
||||||
return outputFileName;
|
return outputFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +257,6 @@ public class PipelineDirectoryProcessor {
|
|||||||
.replace("{outputFolder}", finishedFoldersDir)
|
.replace("{outputFolder}", finishedFoldersDir)
|
||||||
.replace("{folderName}", dir.toString())
|
.replace("{folderName}", dir.toString())
|
||||||
.replaceAll("\\\\?watchedFolders", "");
|
.replaceAll("\\\\?watchedFolders", "");
|
||||||
|
|
||||||
return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(".", outputDir);
|
return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(".", outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
for (File file : filesToProcess) {
|
for (File file : filesToProcess) {
|
||||||
Files.deleteIfExists(processingDir.resolve(file.getName()));
|
Files.deleteIfExists(processingDir.resolve(file.getName()));
|
||||||
logger.info("Deleted original file: {}", file.getName());
|
log.info("Deleted original file: {}", file.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,12 +272,12 @@ public class PipelineDirectoryProcessor {
|
|||||||
for (File file : filesToProcess) {
|
for (File file : filesToProcess) {
|
||||||
try {
|
try {
|
||||||
Files.move(processingDir.resolve(file.getName()), file.toPath());
|
Files.move(processingDir.resolve(file.getName()), file.toPath());
|
||||||
logger.info(
|
log.info(
|
||||||
"Moved file back to original location: {} , {}",
|
"Moved file back to original location: {} , {}",
|
||||||
file.toPath(),
|
file.toPath(),
|
||||||
file.getName());
|
file.getName());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error moving file back to original location: {}", file.getName(), e);
|
log.error("Error moving file back to original location: {}", file.getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -19,17 +15,10 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.*;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
@@ -40,22 +29,49 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.github.pixee.security.ZipSecurity;
|
import io.github.pixee.security.ZipSecurity;
|
||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
import stirling.software.SPDF.SPdfApplication;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class PipelineProcessor {
|
public class PipelineProcessor {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PipelineProcessor.class);
|
private final ApiDocService apiDocService;
|
||||||
|
|
||||||
@Autowired private ApiDocService apiDocService;
|
private final UserServiceInterface userService;
|
||||||
|
|
||||||
@Autowired(required = false)
|
private final ServletContext servletContext;
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
|
||||||
@Autowired private ServletContext servletContext;
|
public PipelineProcessor(
|
||||||
|
ApiDocService apiDocService,
|
||||||
|
@Autowired(required = false) UserServiceInterface userService,
|
||||||
|
ServletContext servletContext) {
|
||||||
|
this.apiDocService = apiDocService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.servletContext = servletContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String removeTrailingNaming(String filename) {
|
||||||
|
// Splitting filename into name and extension
|
||||||
|
int dotIndex = filename.lastIndexOf(".");
|
||||||
|
if (dotIndex == -1) {
|
||||||
|
// No extension found
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
String name = filename.substring(0, dotIndex);
|
||||||
|
String extension = filename.substring(dotIndex);
|
||||||
|
// Finding the last underscore
|
||||||
|
int underscoreIndex = name.lastIndexOf("_");
|
||||||
|
if (underscoreIndex == -1) {
|
||||||
|
// No underscore found
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
// Removing the last part and reattaching the extension
|
||||||
|
return name.substring(0, underscoreIndex) + extension;
|
||||||
|
}
|
||||||
|
|
||||||
private String getApiKeyForUser() {
|
private String getApiKeyForUser() {
|
||||||
if (userService == null) return "";
|
if (userService == null) return "";
|
||||||
@@ -65,23 +81,18 @@ public class PipelineProcessor {
|
|||||||
private String getBaseUrl() {
|
private String getBaseUrl() {
|
||||||
String contextPath = servletContext.getContextPath();
|
String contextPath = servletContext.getContextPath();
|
||||||
String port = SPdfApplication.getStaticPort();
|
String port = SPdfApplication.getStaticPort();
|
||||||
|
|
||||||
return "http://localhost:" + port + contextPath + "/";
|
return "http://localhost:" + port + contextPath + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Resource> runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config)
|
List<Resource> runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
PrintStream logPrintStream = new PrintStream(logStream);
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
boolean hasErrors = false;
|
boolean hasErrors = false;
|
||||||
|
|
||||||
for (PipelineOperation pipelineOperation : config.getOperations()) {
|
for (PipelineOperation pipelineOperation : config.getOperations()) {
|
||||||
String operation = pipelineOperation.getOperation();
|
String operation = pipelineOperation.getOperation();
|
||||||
boolean isMultiInputOperation = apiDocService.isMultiInput(operation);
|
boolean isMultiInputOperation = apiDocService.isMultiInput(operation);
|
||||||
|
log.info(
|
||||||
logger.info(
|
|
||||||
"Running operation: {} isMultiInputOperation {}",
|
"Running operation: {} isMultiInputOperation {}",
|
||||||
operation,
|
operation,
|
||||||
isMultiInputOperation);
|
isMultiInputOperation);
|
||||||
@@ -91,9 +102,7 @@ public class PipelineProcessor {
|
|||||||
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
|
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
|
||||||
}
|
}
|
||||||
// List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
|
// List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
|
||||||
|
|
||||||
String url = getBaseUrl() + operation;
|
String url = getBaseUrl() + operation;
|
||||||
|
|
||||||
List<Resource> newOutputFiles = new ArrayList<>();
|
List<Resource> newOutputFiles = new ArrayList<>();
|
||||||
if (!isMultiInputOperation) {
|
if (!isMultiInputOperation) {
|
||||||
for (Resource file : outputFiles) {
|
for (Resource file : outputFiles) {
|
||||||
@@ -103,7 +112,6 @@ public class PipelineProcessor {
|
|||||||
hasInputFileType = true;
|
hasInputFileType = true;
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
body.add("fileInput", file);
|
body.add("fileInput", file);
|
||||||
|
|
||||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
if (entry.getValue() instanceof List) {
|
if (entry.getValue() instanceof List) {
|
||||||
List<?> list = (List<?>) entry.getValue();
|
List<?> list = (List<?>) entry.getValue();
|
||||||
@@ -114,9 +122,7 @@ public class PipelineProcessor {
|
|||||||
body.add(entry.getKey(), entry.getValue());
|
body.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
|
|
||||||
// If the operation is filter and the response body is null or empty,
|
// If the operation is filter and the response body is null or empty,
|
||||||
// skip
|
// skip
|
||||||
// this
|
// this
|
||||||
@@ -124,10 +130,9 @@ public class PipelineProcessor {
|
|||||||
if (operation.startsWith("filter-")
|
if (operation.startsWith("filter-")
|
||||||
&& (response.getBody() == null
|
&& (response.getBody() == null
|
||||||
|| response.getBody().length == 0)) {
|
|| response.getBody().length == 0)) {
|
||||||
logger.info("Skipping file due to failing {}", operation);
|
log.info("Skipping file due to failing {}", operation);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
logPrintStream.println("Error: " + response.getBody());
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
@@ -136,7 +141,6 @@ public class PipelineProcessor {
|
|||||||
processOutputFiles(operation, response, newOutputFiles);
|
processOutputFiles(operation, response, newOutputFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasInputFileType) {
|
if (!hasInputFileType) {
|
||||||
logPrintStream.println(
|
logPrintStream.println(
|
||||||
"No files with extension "
|
"No files with extension "
|
||||||
@@ -146,7 +150,6 @@ public class PipelineProcessor {
|
|||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Filter and collect all files that match the inputFileExtension
|
// Filter and collect all files that match the inputFileExtension
|
||||||
List<Resource> matchingFiles;
|
List<Resource> matchingFiles;
|
||||||
@@ -162,17 +165,14 @@ public class PipelineProcessor {
|
|||||||
.anyMatch(file.getFilename()::endsWith))
|
.anyMatch(file.getFilename()::endsWith))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are matching files
|
// Check if there are matching files
|
||||||
if (!matchingFiles.isEmpty()) {
|
if (!matchingFiles.isEmpty()) {
|
||||||
// Create a new MultiValueMap for the request body
|
// Create a new MultiValueMap for the request body
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
// Add all matching files to the body
|
// Add all matching files to the body
|
||||||
for (Resource file : matchingFiles) {
|
for (Resource file : matchingFiles) {
|
||||||
body.add("fileInput", file);
|
body.add("fileInput", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
if (entry.getValue() instanceof List) {
|
if (entry.getValue() instanceof List) {
|
||||||
List<?> list = (List<?>) entry.getValue();
|
List<?> list = (List<?>) entry.getValue();
|
||||||
@@ -183,9 +183,7 @@ public class PipelineProcessor {
|
|||||||
body.add(entry.getKey(), entry.getValue());
|
body.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
|
|
||||||
// Handle the response
|
// Handle the response
|
||||||
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
processOutputFiles(operation, response, newOutputFiles);
|
processOutputFiles(operation, response, newOutputFiles);
|
||||||
@@ -208,50 +206,24 @@ public class PipelineProcessor {
|
|||||||
outputFiles = newOutputFiles;
|
outputFiles = newOutputFiles;
|
||||||
}
|
}
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
log.error("Errors occurred during processing. Log: {}", logStream.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputFiles;
|
return outputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) {
|
private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) {
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
// Set up headers, including API key
|
// Set up headers, including API key
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
String apiKey = getApiKeyForUser();
|
String apiKey = getApiKeyForUser();
|
||||||
headers.add("X-API-KEY", apiKey);
|
headers.add("X-API-KEY", apiKey);
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
// Create HttpEntity with the body and headers
|
// Create HttpEntity with the body and headers
|
||||||
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
// Make the request to the REST endpoint
|
// Make the request to the REST endpoint
|
||||||
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String removeTrailingNaming(String filename) {
|
|
||||||
// Splitting filename into name and extension
|
|
||||||
int dotIndex = filename.lastIndexOf(".");
|
|
||||||
if (dotIndex == -1) {
|
|
||||||
// No extension found
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
String name = filename.substring(0, dotIndex);
|
|
||||||
String extension = filename.substring(dotIndex);
|
|
||||||
|
|
||||||
// Finding the last underscore
|
|
||||||
int underscoreIndex = name.lastIndexOf("_");
|
|
||||||
if (underscoreIndex == -1) {
|
|
||||||
// No underscore found
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removing the last part and reattaching the extension
|
|
||||||
return name.substring(0, underscoreIndex) + extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Resource> processOutputFiles(
|
private List<Resource> processOutputFiles(
|
||||||
String operation, ResponseEntity<byte[]> response, List<Resource> newOutputFiles)
|
String operation, ResponseEntity<byte[]> response, List<Resource> newOutputFiles)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -261,13 +233,11 @@ public class PipelineProcessor {
|
|||||||
// If the operation is "auto-rename", generate a new filename.
|
// If the operation is "auto-rename", generate a new filename.
|
||||||
// This is a simple example of generating a filename using current timestamp.
|
// This is a simple example of generating a filename using current timestamp.
|
||||||
// Modify as per your needs.
|
// Modify as per your needs.
|
||||||
|
|
||||||
newFilename = extractFilename(response);
|
newFilename = extractFilename(response);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, keep the original filename.
|
// Otherwise, keep the original filename.
|
||||||
newFilename = removeTrailingNaming(extractFilename(response));
|
newFilename = removeTrailingNaming(extractFilename(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the response body is a zip file
|
// Check if the response body is a zip file
|
||||||
if (isZip(response.getBody())) {
|
if (isZip(response.getBody())) {
|
||||||
// Unzip the file and add all the files to the new output files
|
// Unzip the file and add all the files to the new output files
|
||||||
@@ -275,6 +245,7 @@ public class PipelineProcessor {
|
|||||||
} else {
|
} else {
|
||||||
Resource outputResource =
|
Resource outputResource =
|
||||||
new ByteArrayResource(response.getBody()) {
|
new ByteArrayResource(response.getBody()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return newFilename;
|
return newFilename;
|
||||||
@@ -282,16 +253,14 @@ public class PipelineProcessor {
|
|||||||
};
|
};
|
||||||
newOutputFiles.add(outputResource);
|
newOutputFiles.add(outputResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newOutputFiles;
|
return newOutputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String extractFilename(ResponseEntity<byte[]> response) {
|
public String extractFilename(ResponseEntity<byte[]> response) {
|
||||||
String filename = "default-filename.ext"; // Default filename if not found
|
// Default filename if not found
|
||||||
|
String filename = "default-filename.ext";
|
||||||
HttpHeaders headers = response.getHeaders();
|
HttpHeaders headers = response.getHeaders();
|
||||||
String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||||
|
|
||||||
if (contentDisposition != null && !contentDisposition.isEmpty()) {
|
if (contentDisposition != null && !contentDisposition.isEmpty()) {
|
||||||
String[] parts = contentDisposition.split(";");
|
String[] parts = contentDisposition.split(";");
|
||||||
for (String part : parts) {
|
for (String part : parts) {
|
||||||
@@ -299,30 +268,27 @@ public class PipelineProcessor {
|
|||||||
// Extracts filename and removes quotes if present
|
// Extracts filename and removes quotes if present
|
||||||
filename = part.split("=")[1].trim().replace("\"", "");
|
filename = part.split("=")[1].trim().replace("\"", "");
|
||||||
filename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
|
filename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Resource> generateInputFiles(File[] files) throws Exception {
|
List<Resource> generateInputFiles(File[] files) throws Exception {
|
||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
logger.info("No files");
|
log.info("No files");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Resource> outputFiles = new ArrayList<>();
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
Path path = Paths.get(file.getAbsolutePath());
|
Path path = Paths.get(file.getAbsolutePath());
|
||||||
logger.info("Reading file: " + path); // debug statement
|
// debug statement
|
||||||
|
log.info("Reading file: " + path);
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
Resource fileResource =
|
Resource fileResource =
|
||||||
new ByteArrayResource(Files.readAllBytes(path)) {
|
new ByteArrayResource(Files.readAllBytes(path)) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return file.getName();
|
return file.getName();
|
||||||
@@ -330,24 +296,23 @@ public class PipelineProcessor {
|
|||||||
};
|
};
|
||||||
outputFiles.add(fileResource);
|
outputFiles.add(fileResource);
|
||||||
} else {
|
} else {
|
||||||
logger.info("File not found: " + path);
|
log.info("File not found: " + path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info("Files successfully loaded. Starting processing...");
|
log.info("Files successfully loaded. Starting processing...");
|
||||||
return outputFiles;
|
return outputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Resource> generateInputFiles(MultipartFile[] files) throws Exception {
|
List<Resource> generateInputFiles(MultipartFile[] files) throws Exception {
|
||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
logger.info("No files");
|
log.info("No files");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Resource> outputFiles = new ArrayList<>();
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
Resource fileResource =
|
Resource fileResource =
|
||||||
new ByteArrayResource(file.getBytes()) {
|
new ByteArrayResource(file.getBytes()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return Filenames.toSimpleFileName(file.getOriginalFilename());
|
return Filenames.toSimpleFileName(file.getOriginalFilename());
|
||||||
@@ -355,7 +320,7 @@ public class PipelineProcessor {
|
|||||||
};
|
};
|
||||||
outputFiles.add(fileResource);
|
outputFiles.add(fileResource);
|
||||||
}
|
}
|
||||||
logger.info("Files successfully loaded. Starting processing...");
|
log.info("Files successfully loaded. Starting processing...");
|
||||||
return outputFiles;
|
return outputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,48 +328,42 @@ public class PipelineProcessor {
|
|||||||
if (data == null || data.length < 4) {
|
if (data == null || data.length < 4) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the first four bytes of the data against the standard zip magic number
|
// Check the first four bytes of the data against the standard zip magic number
|
||||||
return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
|
return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> unzip(byte[] data) throws IOException {
|
private List<Resource> unzip(byte[] data) throws IOException {
|
||||||
logger.info("Unzipping data of length: {}", data.length);
|
log.info("Unzipping data of length: {}", data.length);
|
||||||
List<Resource> unzippedFiles = new ArrayList<>();
|
List<Resource> unzippedFiles = new ArrayList<>();
|
||||||
|
|
||||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
ZipInputStream zis = ZipSecurity.createHardenedInputStream(bais)) {
|
ZipInputStream zis = ZipSecurity.createHardenedInputStream(bais)) {
|
||||||
|
|
||||||
ZipEntry entry;
|
ZipEntry entry;
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
while ((count = zis.read(buffer)) != -1) {
|
while ((count = zis.read(buffer)) != -1) {
|
||||||
baos.write(buffer, 0, count);
|
baos.write(buffer, 0, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String filename = entry.getName();
|
final String filename = entry.getName();
|
||||||
Resource fileResource =
|
Resource fileResource =
|
||||||
new ByteArrayResource(baos.toByteArray()) {
|
new ByteArrayResource(baos.toByteArray()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the unzipped file is a zip file, unzip it
|
// If the unzipped file is a zip file, unzip it
|
||||||
if (isZip(baos.toByteArray())) {
|
if (isZip(baos.toByteArray())) {
|
||||||
logger.info("File {} is a zip file. Unzipping...", filename);
|
log.info("File {} is a zip file. Unzipping...", filename);
|
||||||
unzippedFiles.addAll(unzip(baos.toByteArray()));
|
unzippedFiles.addAll(unzip(baos.toByteArray()));
|
||||||
} else {
|
} else {
|
||||||
unzippedFiles.add(fileResource);
|
unzippedFiles.add(fileResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
|
||||||
logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
|
|
||||||
return unzippedFiles;
|
return unzippedFiles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.security.KeyStore;
|
import java.security.*;
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.Security;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
@@ -63,8 +52,6 @@ import org.bouncycastle.operator.InputDecryptorProvider;
|
|||||||
import org.bouncycastle.operator.OperatorCreationException;
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
import org.bouncycastle.pkcs.PKCSException;
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -78,21 +65,166 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/security")
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class CertSignController {
|
public class CertSignController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sign(
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
|
byte[] input,
|
||||||
|
OutputStream output,
|
||||||
|
CreateSignature instance,
|
||||||
|
Boolean showSignature,
|
||||||
|
Integer pageNumber,
|
||||||
|
String name,
|
||||||
|
String location,
|
||||||
|
String reason,
|
||||||
|
Boolean showLogo) {
|
||||||
|
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
||||||
|
PDSignature signature = new PDSignature();
|
||||||
|
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||||
|
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
||||||
|
signature.setName(name);
|
||||||
|
signature.setLocation(location);
|
||||||
|
signature.setReason(reason);
|
||||||
|
signature.setSignDate(Calendar.getInstance());
|
||||||
|
|
||||||
|
if (showSignature) {
|
||||||
|
SignatureOptions signatureOptions = new SignatureOptions();
|
||||||
|
signatureOptions.setVisualSignature(
|
||||||
|
instance.createVisibleSignature(doc, signature, pageNumber, showLogo));
|
||||||
|
signatureOptions.setPage(pageNumber);
|
||||||
|
|
||||||
|
doc.addSignature(signature, instance, signatureOptions);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
doc.addSignature(signature, instance);
|
||||||
|
}
|
||||||
|
doc.saveIncremental(output);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
|
@Operation(
|
||||||
|
summary = "Sign PDF with a Digital Certificate",
|
||||||
|
description =
|
||||||
|
"This endpoint accepts a PDF file, a digital certificate and related information to sign"
|
||||||
|
+ " the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF"
|
||||||
|
+ " Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||||
|
throws Exception {
|
||||||
|
MultipartFile pdf = request.getFileInput();
|
||||||
|
String certType = request.getCertType();
|
||||||
|
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||||
|
MultipartFile certFile = request.getCertFile();
|
||||||
|
MultipartFile p12File = request.getP12File();
|
||||||
|
MultipartFile jksfile = request.getJksFile();
|
||||||
|
String password = request.getPassword();
|
||||||
|
Boolean showSignature = request.isShowSignature();
|
||||||
|
String reason = request.getReason();
|
||||||
|
String location = request.getLocation();
|
||||||
|
String name = request.getName();
|
||||||
|
Integer pageNumber = request.getPageNumber() - 1;
|
||||||
|
Boolean showLogo = request.isShowLogo();
|
||||||
|
|
||||||
|
if (certType == null) {
|
||||||
|
throw new IllegalArgumentException("Cert type must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStore ks = null;
|
||||||
|
|
||||||
|
switch (certType) {
|
||||||
|
case "PEM":
|
||||||
|
ks = KeyStore.getInstance("JKS");
|
||||||
|
ks.load(null);
|
||||||
|
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
|
||||||
|
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
|
||||||
|
ks.setKeyEntry(
|
||||||
|
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
|
||||||
|
break;
|
||||||
|
case "PKCS12":
|
||||||
|
ks = KeyStore.getInstance("PKCS12");
|
||||||
|
ks.load(p12File.getInputStream(), password.toCharArray());
|
||||||
|
break;
|
||||||
|
case "JKS":
|
||||||
|
ks = KeyStore.getInstance("JKS");
|
||||||
|
ks.load(jksfile.getInputStream(), password.toCharArray());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid cert type: " + certType);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
sign(
|
||||||
|
pdfDocumentFactory,
|
||||||
|
pdf.getBytes(),
|
||||||
|
baos,
|
||||||
|
createSignature,
|
||||||
|
showSignature,
|
||||||
|
pageNumber,
|
||||||
|
name,
|
||||||
|
location,
|
||||||
|
reason,
|
||||||
|
showLogo);
|
||||||
|
return WebResponseUtils.boasToWebResponse(
|
||||||
|
baos,
|
||||||
|
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_signed.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
||||||
|
throws IOException, OperatorCreationException, PKCSException {
|
||||||
|
try (PEMParser pemParser =
|
||||||
|
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
|
||||||
|
Object pemObject = pemParser.readObject();
|
||||||
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||||
|
PrivateKeyInfo pkInfo;
|
||||||
|
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
||||||
|
InputDecryptorProvider decProv =
|
||||||
|
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
||||||
|
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
||||||
|
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
||||||
|
PEMDecryptorProvider decProv =
|
||||||
|
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
||||||
|
pkInfo =
|
||||||
|
((PEMEncryptedKeyPair) pemObject)
|
||||||
|
.decryptKeyPair(decProv)
|
||||||
|
.getPrivateKeyInfo();
|
||||||
|
} else {
|
||||||
|
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
||||||
|
}
|
||||||
|
return converter.getPrivateKey(pkInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Certificate getCertificateFromPEM(byte[] pemBytes)
|
||||||
|
throws IOException, CertificateException {
|
||||||
|
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) {
|
||||||
|
return CertificateFactory.getInstance("X.509").generateCertificate(bis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CreateSignature extends CreateSignatureBase {
|
class CreateSignature extends CreateSignatureBase {
|
||||||
File logoFile;
|
File logoFile;
|
||||||
|
|
||||||
@@ -108,7 +240,7 @@ public class CertSignController {
|
|||||||
logoFile = Files.createTempFile("signature", ".png").toFile();
|
logoFile = Files.createTempFile("signature", ".png").toFile();
|
||||||
FileUtils.copyInputStreamToFile(is, logoFile);
|
FileUtils.copyInputStreamToFile(is, logoFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to load image signature file");
|
log.error("Failed to load image signature file");
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,147 +332,4 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
|
||||||
@Operation(
|
|
||||||
summary = "Sign PDF with a Digital Certificate",
|
|
||||||
description =
|
|
||||||
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
|
||||||
throws Exception {
|
|
||||||
MultipartFile pdf = request.getFileInput();
|
|
||||||
String certType = request.getCertType();
|
|
||||||
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
|
||||||
MultipartFile certFile = request.getCertFile();
|
|
||||||
MultipartFile p12File = request.getP12File();
|
|
||||||
MultipartFile jksfile = request.getJksFile();
|
|
||||||
String password = request.getPassword();
|
|
||||||
Boolean showSignature = request.isShowSignature();
|
|
||||||
String reason = request.getReason();
|
|
||||||
String location = request.getLocation();
|
|
||||||
String name = request.getName();
|
|
||||||
Integer pageNumber = request.getPageNumber() - 1;
|
|
||||||
Boolean showLogo = request.isShowLogo();
|
|
||||||
|
|
||||||
if (certType == null) {
|
|
||||||
throw new IllegalArgumentException("Cert type must be provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyStore ks = null;
|
|
||||||
|
|
||||||
switch (certType) {
|
|
||||||
case "PEM":
|
|
||||||
ks = KeyStore.getInstance("JKS");
|
|
||||||
ks.load(null);
|
|
||||||
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
|
|
||||||
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
|
|
||||||
ks.setKeyEntry(
|
|
||||||
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
|
|
||||||
break;
|
|
||||||
case "PKCS12":
|
|
||||||
ks = KeyStore.getInstance("PKCS12");
|
|
||||||
ks.load(p12File.getInputStream(), password.toCharArray());
|
|
||||||
break;
|
|
||||||
case "JKS":
|
|
||||||
ks = KeyStore.getInstance("JKS");
|
|
||||||
ks.load(jksfile.getInputStream(), password.toCharArray());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid cert type: " + certType);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
sign(
|
|
||||||
pdfDocumentFactory,
|
|
||||||
pdf.getBytes(),
|
|
||||||
baos,
|
|
||||||
createSignature,
|
|
||||||
showSignature,
|
|
||||||
pageNumber,
|
|
||||||
name,
|
|
||||||
location,
|
|
||||||
reason,
|
|
||||||
showLogo);
|
|
||||||
return WebResponseUtils.boasToWebResponse(
|
|
||||||
baos,
|
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_signed.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sign(
|
|
||||||
CustomPDDocumentFactory pdfDocumentFactory,
|
|
||||||
byte[] input,
|
|
||||||
OutputStream output,
|
|
||||||
CreateSignature instance,
|
|
||||||
Boolean showSignature,
|
|
||||||
Integer pageNumber,
|
|
||||||
String name,
|
|
||||||
String location,
|
|
||||||
String reason,
|
|
||||||
Boolean showLogo) {
|
|
||||||
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
|
||||||
PDSignature signature = new PDSignature();
|
|
||||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
|
||||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
|
||||||
signature.setName(name);
|
|
||||||
signature.setLocation(location);
|
|
||||||
signature.setReason(reason);
|
|
||||||
signature.setSignDate(Calendar.getInstance());
|
|
||||||
|
|
||||||
if (showSignature) {
|
|
||||||
SignatureOptions signatureOptions = new SignatureOptions();
|
|
||||||
signatureOptions.setVisualSignature(
|
|
||||||
instance.createVisibleSignature(doc, signature, pageNumber, showLogo));
|
|
||||||
signatureOptions.setPage(pageNumber);
|
|
||||||
|
|
||||||
doc.addSignature(signature, instance, signatureOptions);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
doc.addSignature(signature, instance);
|
|
||||||
}
|
|
||||||
doc.saveIncremental(output);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
|
||||||
throws IOException, OperatorCreationException, PKCSException {
|
|
||||||
try (PEMParser pemParser =
|
|
||||||
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
|
|
||||||
Object pemObject = pemParser.readObject();
|
|
||||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
|
||||||
PrivateKeyInfo pkInfo;
|
|
||||||
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
|
||||||
InputDecryptorProvider decProv =
|
|
||||||
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
|
||||||
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
|
||||||
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
|
||||||
PEMDecryptorProvider decProv =
|
|
||||||
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
|
||||||
pkInfo =
|
|
||||||
((PEMEncryptedKeyPair) pemObject)
|
|
||||||
.decryptKeyPair(decProv)
|
|
||||||
.getPrivateKeyInfo();
|
|
||||||
} else {
|
|
||||||
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
|
||||||
}
|
|
||||||
return converter.getPrivateKey(pkInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Certificate getCertificateFromPEM(byte[] pemBytes)
|
|
||||||
throws IOException, CertificateException {
|
|
||||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) {
|
|
||||||
return CertificateFactory.getInstance("X.509").generateCertificate(bis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,13 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSInputStream;
|
import org.apache.pdfbox.cos.COSInputStream;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.cos.COSString;
|
import org.apache.pdfbox.cos.COSString;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDJavascriptNameTreeNode;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDStream;
|
import org.apache.pdfbox.pdmodel.common.PDStream;
|
||||||
@@ -56,8 +44,6 @@ import org.apache.xmpbox.XMPMetadata;
|
|||||||
import org.apache.xmpbox.xml.DomXmpParser;
|
import org.apache.xmpbox.xml.DomXmpParser;
|
||||||
import org.apache.xmpbox.xml.XmpParsingException;
|
import org.apache.xmpbox.xml.XmpParsingException;
|
||||||
import org.apache.xmpbox.xml.XmpSerializer;
|
import org.apache.xmpbox.xml.XmpSerializer;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -73,18 +59,60 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/security")
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class GetInfoOnPDF {
|
public class GetInfoOnPDF {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(GetInfoOnPDF.class);
|
|
||||||
|
|
||||||
static ObjectMapper objectMapper = new ObjectMapper();
|
static ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
||||||
|
if (outline == null) return;
|
||||||
|
|
||||||
|
ObjectNode outlineNode = objectMapper.createObjectNode();
|
||||||
|
outlineNode.put("Title", outline.getTitle());
|
||||||
|
// You can add other properties if needed
|
||||||
|
arrayNode.add(outlineNode);
|
||||||
|
|
||||||
|
PDOutlineItem child = outline.getFirstChild();
|
||||||
|
while (child != null) {
|
||||||
|
addOutlinesToArray(child, arrayNode);
|
||||||
|
child = child.getNextSibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||||
|
// Check XMP Metadata
|
||||||
|
try {
|
||||||
|
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||||
|
if (pdMetadata != null) {
|
||||||
|
COSInputStream metaStream = pdMetadata.createInputStream();
|
||||||
|
DomXmpParser domXmpParser = new DomXmpParser();
|
||||||
|
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
new XmpSerializer().serialize(xmpMeta, baos, true);
|
||||||
|
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if (xmpString.contains(standardKeyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
Exception
|
||||||
|
e) { // Catching general exception for brevity, ideally you'd catch specific
|
||||||
|
// exceptions.
|
||||||
|
log.error("exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||||
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
||||||
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
||||||
@@ -224,7 +252,7 @@ public class GetInfoOnPDF {
|
|||||||
javascriptArray.add(jsNode);
|
javascriptArray.add(jsNode);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +285,7 @@ public class GetInfoOnPDF {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
|
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
|
||||||
@@ -309,7 +337,7 @@ public class GetInfoOnPDF {
|
|||||||
new XmpSerializer().serialize(xmpMeta, os, true);
|
new XmpSerializer().serialize(xmpMeta, os, true);
|
||||||
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
|
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
|
||||||
} catch (XmpParsingException | IOException e) {
|
} catch (XmpParsingException | IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +613,7 @@ public class GetInfoOnPDF {
|
|||||||
MediaType.APPLICATION_JSON);
|
MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -608,21 +636,6 @@ public class GetInfoOnPDF {
|
|||||||
return state ? "Allowed" : "Not Allowed";
|
return state ? "Allowed" : "Not Allowed";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
|
||||||
if (outline == null) return;
|
|
||||||
|
|
||||||
ObjectNode outlineNode = objectMapper.createObjectNode();
|
|
||||||
outlineNode.put("Title", outline.getTitle());
|
|
||||||
// You can add other properties if needed
|
|
||||||
arrayNode.add(outlineNode);
|
|
||||||
|
|
||||||
PDOutlineItem child = outline.getFirstChild();
|
|
||||||
while (child != null) {
|
|
||||||
addOutlinesToArray(child, arrayNode);
|
|
||||||
child = child.getNextSibling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPageOrientation(double width, double height) {
|
public String getPageOrientation(double width, double height) {
|
||||||
if (width > height) {
|
if (width > height) {
|
||||||
return "Landscape";
|
return "Landscape";
|
||||||
@@ -680,33 +693,6 @@ public class GetInfoOnPDF {
|
|||||||
return dimensionInfo;
|
return dimensionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
|
||||||
// Check XMP Metadata
|
|
||||||
try {
|
|
||||||
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
|
||||||
if (pdMetadata != null) {
|
|
||||||
COSInputStream metaStream = pdMetadata.createInputStream();
|
|
||||||
DomXmpParser domXmpParser = new DomXmpParser();
|
|
||||||
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
new XmpSerializer().serialize(xmpMeta, baos, true);
|
|
||||||
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
if (xmpString.contains(standardKeyword)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (
|
|
||||||
Exception
|
|
||||||
e) { // Catching general exception for brevity, ideally you'd catch specific
|
|
||||||
// exceptions.
|
|
||||||
logger.error("exception", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
||||||
ArrayNode elementsArray = objectMapper.createArrayNode();
|
ArrayNode elementsArray = objectMapper.createArrayNode();
|
||||||
if (nodes != null) {
|
if (nodes != null) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import java.io.IOException;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -29,8 +27,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class PasswordController {
|
public class PasswordController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -39,7 +35,11 @@ public class PasswordController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
@Operation(
|
||||||
|
summary = "Remove password from a PDF file",
|
||||||
|
description =
|
||||||
|
"This endpoint removes the password from a protected PDF file. Users need to provide the"
|
||||||
|
+ " existing password. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@@ -49,12 +49,16 @@ public class PasswordController {
|
|||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_password_removed.pdf");
|
+ "_password_removed.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
@Operation(
|
||||||
|
summary = "Add password to a PDF file",
|
||||||
|
description =
|
||||||
|
"This endpoint adds password protection to a PDF file. Users can specify a set of"
|
||||||
|
+ " permissions that should be applied to the file. Input:PDF Output:PDF")
|
||||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@@ -92,12 +96,12 @@ public class PasswordController {
|
|||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_permissions.pdf");
|
+ "_permissions.pdf");
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_passworded.pdf");
|
+ "_passworded.pdf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -8,8 +8,6 @@ import java.util.List;
|
|||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||||
import stirling.software.SPDF.pdf.TextFinder;
|
import stirling.software.SPDF.pdf.TextFinder;
|
||||||
@@ -31,11 +30,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/security")
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class RedactController {
|
public class RedactController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -47,7 +45,8 @@ public class RedactController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Redacts listOfText in a PDF document",
|
summary = "Redacts listOfText in a PDF document",
|
||||||
description =
|
description =
|
||||||
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
|
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF,"
|
||||||
|
+ " Output:PDF, Type:SISO")
|
||||||
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request)
|
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
@@ -68,7 +67,7 @@ public class RedactController {
|
|||||||
}
|
}
|
||||||
redactColor = Color.decode(colorString);
|
redactColor = Color.decode(colorString);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
logger.warn("Invalid color string provided. Using default color BLACK for redaction.");
|
log.warn("Invalid color string provided. Using default color BLACK for redaction.");
|
||||||
redactColor = Color.BLACK;
|
redactColor = Color.BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -31,8 +29,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class RemoveCertSignController {
|
public class RemoveCertSignController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
|
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -44,7 +40,8 @@ public class RemoveCertSignController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove digital signature from PDF",
|
summary = "Remove digital signature from PDF",
|
||||||
description =
|
description =
|
||||||
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input: PDF, Output: PDF")
|
"This endpoint accepts a PDF file and returns the PDF file without the digital signature."
|
||||||
|
+ " Input: PDF, Output: PDF")
|
||||||
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
|
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile pdf = request.getFileInput();
|
MultipartFile pdf = request.getFileInput();
|
||||||
|
|||||||
@@ -4,17 +4,9 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
import org.apache.pdfbox.pdmodel.interactive.action.*;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
import org.bouncycastle.cms.CMSProcessable;
|
import org.bouncycastle.cms.*;
|
||||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
|
||||||
import org.bouncycastle.cms.CMSSignedData;
|
|
||||||
import org.bouncycastle.cms.SignerInformation;
|
|
||||||
import org.bouncycastle.cms.SignerInformationStore;
|
|
||||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||||
import org.bouncycastle.util.Store;
|
import org.bouncycastle.util.Store;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@@ -38,24 +37,30 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
@Tag(name = "Account Security", description = "Account Security APIs")
|
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||||
public class AccountWebController {
|
public class AccountWebController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@Autowired SessionPersistentRegistry sessionPersistentRegistry;
|
|
||||||
|
|
||||||
@Autowired
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
private UserRepository userRepository; // Assuming you have a repository for user operations
|
|
||||||
|
private final UserRepository // Assuming you have a repository for user operations
|
||||||
|
userRepository;
|
||||||
|
|
||||||
|
public AccountWebController(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
SessionPersistentRegistry sessionPersistentRegistry,
|
||||||
|
UserRepository userRepository) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/login")
|
@GetMapping("/login")
|
||||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
|
|
||||||
// If the user is already authenticated, redirect them to the home page.
|
// If the user is already authenticated, redirect them to the home page.
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> providerList = new HashMap<>();
|
Map<String, String> providerList = new HashMap<>();
|
||||||
|
|
||||||
Security securityProps = applicationProperties.getSecurity();
|
Security securityProps = applicationProperties.getSecurity();
|
||||||
|
|
||||||
OAUTH2 oauth = securityProps.getOauth2();
|
OAUTH2 oauth = securityProps.getOauth2();
|
||||||
if (oauth != null) {
|
if (oauth != null) {
|
||||||
if (oauth.getEnabled()) {
|
if (oauth.getEnabled()) {
|
||||||
@@ -70,14 +75,12 @@ public class AccountWebController {
|
|||||||
"/oauth2/authorization/" + google.getName(),
|
"/oauth2/authorization/" + google.getName(),
|
||||||
google.getClientName());
|
google.getClientName());
|
||||||
}
|
}
|
||||||
|
|
||||||
GithubProvider github = client.getGithub();
|
GithubProvider github = client.getGithub();
|
||||||
if (github.isSettingsValid()) {
|
if (github.isSettingsValid()) {
|
||||||
providerList.put(
|
providerList.put(
|
||||||
"/oauth2/authorization/" + github.getName(),
|
"/oauth2/authorization/" + github.getName(),
|
||||||
github.getClientName());
|
github.getClientName());
|
||||||
}
|
}
|
||||||
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
if (keycloak.isSettingsValid()) {
|
if (keycloak.isSettingsValid()) {
|
||||||
providerList.put(
|
providerList.put(
|
||||||
@@ -87,7 +90,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML2 saml2 = securityProps.getSaml2();
|
SAML2 saml2 = securityProps.getSaml2();
|
||||||
if (securityProps.isSaml2Activ()
|
if (securityProps.isSaml2Activ()
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
||||||
@@ -98,16 +100,12 @@ public class AccountWebController {
|
|||||||
.entrySet()
|
.entrySet()
|
||||||
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||||
model.addAttribute("providerlist", providerList);
|
model.addAttribute("providerlist", providerList);
|
||||||
|
|
||||||
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
||||||
boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false;
|
boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false;
|
||||||
model.addAttribute("altLogin", altLogin);
|
model.addAttribute("altLogin", altLogin);
|
||||||
|
|
||||||
model.addAttribute("currentPage", "login");
|
model.addAttribute("currentPage", "login");
|
||||||
|
|
||||||
String error = request.getParameter("error");
|
String error = request.getParameter("error");
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case "badcredentials":
|
case "badcredentials":
|
||||||
error = "login.invalid";
|
error = "login.invalid";
|
||||||
@@ -121,12 +119,10 @@ public class AccountWebController {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
}
|
}
|
||||||
String erroroauth = request.getParameter("erroroauth");
|
String erroroauth = request.getParameter("erroroauth");
|
||||||
if (erroroauth != null) {
|
if (erroroauth != null) {
|
||||||
|
|
||||||
switch (erroroauth) {
|
switch (erroroauth) {
|
||||||
case "oauth2AutoCreateDisabled":
|
case "oauth2AutoCreateDisabled":
|
||||||
erroroauth = "login.oauth2AutoCreateDisabled";
|
erroroauth = "login.oauth2AutoCreateDisabled";
|
||||||
@@ -167,8 +163,8 @@ public class AccountWebController {
|
|||||||
case "invalid_destination":
|
case "invalid_destination":
|
||||||
erroroauth = "login.invalid_destination";
|
erroroauth = "login.invalid_destination";
|
||||||
break;
|
break;
|
||||||
// Valid InResponseTo was not available from the validation context, unable to
|
// Valid InResponseTo was not available from the validation context, unable to
|
||||||
// evaluate
|
// evaluate
|
||||||
case "invalid_in_response_to":
|
case "invalid_in_response_to":
|
||||||
erroroauth = "login.invalid_in_response_to";
|
erroroauth = "login.invalid_in_response_to";
|
||||||
break;
|
break;
|
||||||
@@ -178,18 +174,14 @@ public class AccountWebController {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("erroroauth", erroroauth);
|
model.addAttribute("erroroauth", erroroauth);
|
||||||
}
|
}
|
||||||
if (request.getParameter("messageType") != null) {
|
if (request.getParameter("messageType") != null) {
|
||||||
|
|
||||||
model.addAttribute("messageType", "changedCredsMessage");
|
model.addAttribute("messageType", "changedCredsMessage");
|
||||||
}
|
}
|
||||||
if (request.getParameter("logout") != null) {
|
if (request.getParameter("logout") != null) {
|
||||||
|
|
||||||
model.addAttribute("logoutMessage", "You have been logged out.");
|
model.addAttribute("logoutMessage", "You have been logged out.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "login";
|
return "login";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +192,11 @@ public class AccountWebController {
|
|||||||
List<User> allUsers = userRepository.findAll();
|
List<User> allUsers = userRepository.findAll();
|
||||||
Iterator<User> iterator = allUsers.iterator();
|
Iterator<User> iterator = allUsers.iterator();
|
||||||
Map<String, String> roleDetails = Role.getAllRoleDetails();
|
Map<String, String> roleDetails = Role.getAllRoleDetails();
|
||||||
|
|
||||||
// Map to store session information and user activity status
|
// Map to store session information and user activity status
|
||||||
Map<String, Boolean> userSessions = new HashMap<>();
|
Map<String, Boolean> userSessions = new HashMap<>();
|
||||||
Map<String, Date> userLastRequest = new HashMap<>();
|
Map<String, Date> userLastRequest = new HashMap<>();
|
||||||
|
|
||||||
int activeUsers = 0;
|
int activeUsers = 0;
|
||||||
int disabledUsers = 0;
|
int disabledUsers = 0;
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
User user = iterator.next();
|
User user = iterator.next();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
@@ -215,22 +204,20 @@ public class AccountWebController {
|
|||||||
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
|
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
|
||||||
break; // Break out of the inner loop once the user is removed
|
// Break out of the inner loop once the user is removed
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the user's session status and last request time
|
// Determine the user's session status and last request time
|
||||||
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
|
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
|
||||||
boolean hasActiveSession = false;
|
boolean hasActiveSession = false;
|
||||||
Date lastRequest = null;
|
Date lastRequest = null;
|
||||||
|
|
||||||
Optional<SessionEntity> latestSession =
|
Optional<SessionEntity> latestSession =
|
||||||
sessionPersistentRegistry.findLatestSession(user.getUsername());
|
sessionPersistentRegistry.findLatestSession(user.getUsername());
|
||||||
if (latestSession.isPresent()) {
|
if (latestSession.isPresent()) {
|
||||||
SessionEntity sessionEntity = latestSession.get();
|
SessionEntity sessionEntity = latestSession.get();
|
||||||
Date lastAccessedTime = sessionEntity.getLastRequest();
|
Date lastAccessedTime = sessionEntity.getLastRequest();
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
|
|
||||||
// Calculate session expiration and update session status accordingly
|
// Calculate session expiration and update session status accordingly
|
||||||
Instant expirationTime =
|
Instant expirationTime =
|
||||||
lastAccessedTime
|
lastAccessedTime
|
||||||
@@ -242,16 +229,14 @@ public class AccountWebController {
|
|||||||
} else {
|
} else {
|
||||||
hasActiveSession = !sessionEntity.isExpired();
|
hasActiveSession = !sessionEntity.isExpired();
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRequest = sessionEntity.getLastRequest();
|
lastRequest = sessionEntity.getLastRequest();
|
||||||
} else {
|
} else {
|
||||||
hasActiveSession = false;
|
hasActiveSession = false;
|
||||||
lastRequest = new Date(0); // No session, set default last request time
|
// No session, set default last request time
|
||||||
|
lastRequest = new Date(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
userSessions.put(user.getUsername(), hasActiveSession);
|
userSessions.put(user.getUsername(), hasActiveSession);
|
||||||
userLastRequest.put(user.getUsername(), lastRequest);
|
userLastRequest.put(user.getUsername(), lastRequest);
|
||||||
|
|
||||||
if (hasActiveSession) {
|
if (hasActiveSession) {
|
||||||
activeUsers++;
|
activeUsers++;
|
||||||
}
|
}
|
||||||
@@ -260,7 +245,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort users by active status and last request date
|
// Sort users by active status and last request date
|
||||||
List<User> sortedUsers =
|
List<User> sortedUsers =
|
||||||
allUsers.stream()
|
allUsers.stream()
|
||||||
@@ -268,7 +252,6 @@ public class AccountWebController {
|
|||||||
(u1, u2) -> {
|
(u1, u2) -> {
|
||||||
boolean u1Active = userSessions.get(u1.getUsername());
|
boolean u1Active = userSessions.get(u1.getUsername());
|
||||||
boolean u2Active = userSessions.get(u2.getUsername());
|
boolean u2Active = userSessions.get(u2.getUsername());
|
||||||
|
|
||||||
if (u1Active && !u2Active) {
|
if (u1Active && !u2Active) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (!u1Active && u2Active) {
|
} else if (!u1Active && u2Active) {
|
||||||
@@ -284,9 +267,7 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
|
|
||||||
String deleteMessage = null;
|
String deleteMessage = null;
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
@@ -300,7 +281,6 @@ public class AccountWebController {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
model.addAttribute("deleteMessage", deleteMessage);
|
model.addAttribute("deleteMessage", deleteMessage);
|
||||||
|
|
||||||
String addMessage = null;
|
String addMessage = null;
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "usernameExists":
|
case "usernameExists":
|
||||||
@@ -317,7 +297,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("addMessage", addMessage);
|
model.addAttribute("addMessage", addMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
String changeMessage = null;
|
String changeMessage = null;
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
@@ -336,7 +315,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("changeMessage", changeMessage);
|
model.addAttribute("changeMessage", changeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("users", sortedUsers);
|
model.addAttribute("users", sortedUsers);
|
||||||
model.addAttribute("currentUsername", authentication.getName());
|
model.addAttribute("currentUsername", authentication.getName());
|
||||||
model.addAttribute("roleDetails", roleDetails);
|
model.addAttribute("roleDetails", roleDetails);
|
||||||
@@ -357,21 +335,17 @@ public class AccountWebController {
|
|||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
// Cast the principal object to UserDetails
|
// Cast the principal object to UserDetails
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
// Retrieve username and other attributes
|
||||||
username = userDetails.getUsername();
|
username = userDetails.getUsername();
|
||||||
|
|
||||||
// Add oAuth2 Login attributes to the model
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", false);
|
model.addAttribute("oAuth2Login", false);
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User) {
|
||||||
// Cast the principal object to OAuth2User
|
// Cast the principal object to OAuth2User
|
||||||
OAuth2User userDetails = (OAuth2User) principal;
|
OAuth2User userDetails = (OAuth2User) principal;
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
// Retrieve username and other attributes
|
||||||
username =
|
username =
|
||||||
userDetails.getAttribute(
|
userDetails.getAttribute(
|
||||||
@@ -383,22 +357,21 @@ public class AccountWebController {
|
|||||||
// Cast the principal object to OAuth2User
|
// Cast the principal object to OAuth2User
|
||||||
CustomSaml2AuthenticatedPrincipal userDetails =
|
CustomSaml2AuthenticatedPrincipal userDetails =
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
(CustomSaml2AuthenticatedPrincipal) principal;
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
// Retrieve username and other attributes
|
||||||
username = userDetails.getName();
|
username = userDetails.getName();
|
||||||
// Add oAuth2 Login attributes to the model
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
userRepository.findByUsernameIgnoreCaseWithSettings(
|
userRepository
|
||||||
username); // Assuming findByUsername method exists
|
.findByUsernameIgnoreCaseWithSettings( // Assuming findByUsername
|
||||||
|
// method exists
|
||||||
|
username);
|
||||||
if (!user.isPresent()) {
|
if (!user.isPresent()) {
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert settings map to JSON string
|
// Convert settings map to JSON string
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
String settingsJson;
|
String settingsJson;
|
||||||
@@ -409,7 +382,6 @@ public class AccountWebController {
|
|||||||
log.error("exception", e);
|
log.error("exception", e);
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
@@ -433,7 +405,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("messageType", messageType);
|
model.addAttribute("messageType", messageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add attributes to the model
|
// Add attributes to the model
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
model.addAttribute("role", user.get().getRolesAsString());
|
model.addAttribute("role", user.get().getRolesAsString());
|
||||||
@@ -456,23 +427,21 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
// Cast the principal object to UserDetails
|
// Cast the principal object to UserDetails
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
// Retrieve username and other attributes
|
||||||
String username = userDetails.getUsername();
|
String username = userDetails.getUsername();
|
||||||
|
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
userRepository.findByUsernameIgnoreCase(
|
userRepository
|
||||||
username); // Assuming findByUsername method exists
|
.findByUsernameIgnoreCase( // Assuming findByUsername method exists
|
||||||
|
username);
|
||||||
if (!user.isPresent()) {
|
if (!user.isPresent()) {
|
||||||
// Handle error appropriately
|
// Handle error appropriately
|
||||||
return "redirect:/error"; // Example redirection in case of error
|
// Example redirection in case of error
|
||||||
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
@@ -493,7 +462,6 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("messageType", messageType);
|
model.addAttribute("messageType", messageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add attributes to the model
|
// Add attributes to the model
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.web;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -19,25 +18,25 @@ import stirling.software.SPDF.utils.FileInfo;
|
|||||||
@Tag(name = "Database Management", description = "Database management and security APIs")
|
@Tag(name = "Database Management", description = "Database management and security APIs")
|
||||||
public class DatabaseWebController {
|
public class DatabaseWebController {
|
||||||
|
|
||||||
@Autowired private DatabaseBackupHelper databaseBackupHelper;
|
private final DatabaseBackupHelper databaseBackupHelper;
|
||||||
|
|
||||||
|
public DatabaseWebController(DatabaseBackupHelper databaseBackupHelper) {
|
||||||
|
this.databaseBackupHelper = databaseBackupHelper;
|
||||||
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@GetMapping("/database")
|
@GetMapping("/database")
|
||||||
public String database(HttpServletRequest request, Model model, Authentication authentication) {
|
public String database(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
String error = request.getParameter("error");
|
String error = request.getParameter("error");
|
||||||
String confirmed = request.getParameter("infoMessage");
|
String confirmed = request.getParameter("infoMessage");
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
} else if (confirmed != null) {
|
} else if (confirmed != null) {
|
||||||
model.addAttribute("infoMessage", confirmed);
|
model.addAttribute("infoMessage", confirmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FileInfo> backupList = databaseBackupHelper.getBackupList();
|
List<FileInfo> backupList = databaseBackupHelper.getBackupList();
|
||||||
model.addAttribute("backupFiles", backupList);
|
model.addAttribute("backupFiles", backupList);
|
||||||
|
|
||||||
model.addAttribute("databaseVersion", databaseBackupHelper.getH2Version());
|
model.addAttribute("databaseVersion", databaseBackupHelper.getH2Version());
|
||||||
|
|
||||||
return "database";
|
return "database";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
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.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
@@ -31,41 +24,51 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.SignatureFile;
|
import stirling.software.SPDF.model.SignatureFile;
|
||||||
import stirling.software.SPDF.service.SignatureService;
|
import stirling.software.SPDF.service.SignatureService;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
@Slf4j
|
||||||
public class GeneralWebController {
|
public class GeneralWebController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(GeneralWebController.class);
|
private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
|
||||||
|
private static final String ALL_USERS_FOLDER = "ALL_USERS";
|
||||||
|
private final SignatureService signatureService;
|
||||||
|
private final UserServiceInterface userService;
|
||||||
|
private final ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
public GeneralWebController(
|
||||||
|
SignatureService signatureService,
|
||||||
|
@Autowired(required = false) UserServiceInterface userService,
|
||||||
|
ResourceLoader resourceLoader) {
|
||||||
|
this.signatureService = signatureService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/pipeline")
|
@GetMapping("/pipeline")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String pipelineForm(Model model) {
|
public String pipelineForm(Model model) {
|
||||||
model.addAttribute("currentPage", "pipeline");
|
model.addAttribute("currentPage", "pipeline");
|
||||||
|
|
||||||
List<String> pipelineConfigs = new ArrayList<>();
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
|
|
||||||
if (new File("./pipeline/defaultWebUIConfigs/").exists()) {
|
if (new File("./pipeline/defaultWebUIConfigs/").exists()) {
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
||||||
List<Path> jsonFiles =
|
List<Path> jsonFiles =
|
||||||
paths.filter(Files::isRegularFile)
|
paths.filter(Files::isRegularFile)
|
||||||
.filter(p -> p.toString().endsWith(".json"))
|
.filter(p -> p.toString().endsWith(".json"))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (Path jsonFile : jsonFiles) {
|
for (Path jsonFile : jsonFiles) {
|
||||||
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
||||||
pipelineConfigs.add(content);
|
pipelineConfigs.add(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String config : pipelineConfigs) {
|
for (String config : pipelineConfigs) {
|
||||||
Map<String, Object> jsonContent =
|
Map<String, Object> jsonContent =
|
||||||
new ObjectMapper()
|
new ObjectMapper()
|
||||||
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
||||||
|
|
||||||
String name = (String) jsonContent.get("name");
|
String name = (String) jsonContent.get("name");
|
||||||
if (name == null || name.length() < 1) {
|
if (name == null || name.length() < 1) {
|
||||||
String filename =
|
String filename =
|
||||||
@@ -80,9 +83,8 @@ public class GeneralWebController {
|
|||||||
configWithName.put("name", name);
|
configWithName.put("name", name);
|
||||||
pipelineConfigsWithNames.add(configWithName);
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pipelineConfigsWithNames.size() == 0) {
|
if (pipelineConfigsWithNames.size() == 0) {
|
||||||
@@ -92,9 +94,7 @@ public class GeneralWebController {
|
|||||||
pipelineConfigsWithNames.add(configWithName);
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
}
|
}
|
||||||
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
||||||
|
|
||||||
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
||||||
|
|
||||||
return "pipeline";
|
return "pipeline";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,14 +175,6 @@ public class GeneralWebController {
|
|||||||
return "split-pdfs";
|
return "split-pdfs";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
|
|
||||||
private static final String ALL_USERS_FOLDER = "ALL_USERS";
|
|
||||||
|
|
||||||
@Autowired private SignatureService signatureService;
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
|
||||||
@GetMapping("/sign")
|
@GetMapping("/sign")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String signForm(Model model) {
|
public String signForm(Model model) {
|
||||||
@@ -190,10 +182,8 @@ public class GeneralWebController {
|
|||||||
if (userService != null) {
|
if (userService != null) {
|
||||||
username = userService.getCurrentUsername();
|
username = userService.getCurrentUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get signatures from both personal and ALL_USERS folders
|
// Get signatures from both personal and ALL_USERS folders
|
||||||
List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);
|
List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);
|
||||||
|
|
||||||
model.addAttribute("currentPage", "sign");
|
model.addAttribute("currentPage", "sign");
|
||||||
model.addAttribute("fonts", getFontNames());
|
model.addAttribute("fonts", getFontNames());
|
||||||
model.addAttribute("signatures", signatures);
|
model.addAttribute("signatures", signatures);
|
||||||
@@ -228,17 +218,12 @@ public class GeneralWebController {
|
|||||||
return "overlay-pdf";
|
return "overlay-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired private ResourceLoader resourceLoader;
|
|
||||||
|
|
||||||
private List<FontResource> getFontNames() {
|
private List<FontResource> getFontNames() {
|
||||||
List<FontResource> fontNames = new ArrayList<>();
|
List<FontResource> fontNames = new ArrayList<>();
|
||||||
|
|
||||||
// Extract font names from classpath
|
// Extract font names from classpath
|
||||||
fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2"));
|
fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2"));
|
||||||
|
|
||||||
// Extract font names from external directory
|
// Extract font names from external directory
|
||||||
fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*"));
|
fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*"));
|
||||||
|
|
||||||
return fontNames;
|
return fontNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,13 +270,38 @@ public class GeneralWebController {
|
|||||||
case "svg":
|
case "svg":
|
||||||
return "svg";
|
return "svg";
|
||||||
default:
|
default:
|
||||||
return ""; // or throw an exception if an unexpected extension is encountered
|
// or throw an exception if an unexpected extension is encountered
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/crop")
|
||||||
|
@Hidden
|
||||||
|
public String cropForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "crop");
|
||||||
|
return "crop";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/auto-split-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String autoSPlitPDFForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "auto-split-pdf");
|
||||||
|
return "auto-split-pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/remove-image-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String removeImagePdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "remove-image-pdf");
|
||||||
|
return "remove-image-pdf";
|
||||||
|
}
|
||||||
|
|
||||||
public class FontResource {
|
public class FontResource {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private String extension;
|
private String extension;
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
public FontResource(String name, String extension) {
|
public FontResource(String name, String extension) {
|
||||||
@@ -324,25 +334,4 @@ public class GeneralWebController {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/crop")
|
|
||||||
@Hidden
|
|
||||||
public String cropForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "crop");
|
|
||||||
return "crop";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/auto-split-pdf")
|
|
||||||
@Hidden
|
|
||||||
public String autoSPlitPDFForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "auto-split-pdf");
|
|
||||||
return "auto-split-pdf";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/remove-image-pdf")
|
|
||||||
@Hidden
|
|
||||||
public String removeImagePdfForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "remove-image-pdf");
|
|
||||||
return "remove-image-pdf";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -22,13 +19,19 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Dependency;
|
import stirling.software.SPDF.model.Dependency;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@Slf4j
|
||||||
public class HomeWebController {
|
public class HomeWebController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HomeWebController.class);
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public HomeWebController(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/about")
|
@GetMapping("/about")
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -50,7 +53,7 @@ public class HomeWebController {
|
|||||||
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});
|
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});
|
||||||
model.addAttribute("dependencies", data.get("dependencies"));
|
model.addAttribute("dependencies", data.get("dependencies"));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("exception", e);
|
log.error("exception", e);
|
||||||
}
|
}
|
||||||
return "licenses";
|
return "licenses";
|
||||||
}
|
}
|
||||||
@@ -71,8 +74,6 @@ public class HomeWebController {
|
|||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@Hidden
|
@Hidden
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -30,12 +29,18 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class MetricsController {
|
public class MetricsController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private final MeterRegistry meterRegistry;
|
private final MeterRegistry meterRegistry;
|
||||||
|
|
||||||
private boolean metricsEnabled;
|
private boolean metricsEnabled;
|
||||||
|
|
||||||
|
public MetricsController(
|
||||||
|
ApplicationProperties applicationProperties, MeterRegistry meterRegistry) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.meterRegistry = meterRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();
|
Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();
|
||||||
@@ -43,11 +48,6 @@ public class MetricsController {
|
|||||||
this.metricsEnabled = metricsEnabled;
|
this.metricsEnabled = metricsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public MetricsController(MeterRegistry meterRegistry) {
|
|
||||||
this.meterRegistry = meterRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Application status and version",
|
summary = "Application status and version",
|
||||||
@@ -57,7 +57,6 @@ public class MetricsController {
|
|||||||
if (!metricsEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> status = new HashMap<>();
|
Map<String, String> status = new HashMap<>();
|
||||||
status.put("status", "UP");
|
status.put("status", "UP");
|
||||||
status.put("version", getClass().getPackage().getImplementationVersion());
|
status.put("version", getClass().getPackage().getImplementationVersion());
|
||||||
@@ -236,7 +235,6 @@ public class MetricsController {
|
|||||||
String uri = counter.getId().getTag("uri");
|
String uri = counter.getId().getTag("uri");
|
||||||
counts.merge(uri, counter.count(), Double::sum);
|
counts.merge(uri, counter.count(), Double::sum);
|
||||||
});
|
});
|
||||||
|
|
||||||
List<EndpointCount> result =
|
List<EndpointCount> result =
|
||||||
counts.entrySet().stream()
|
counts.entrySet().stream()
|
||||||
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
|
||||||
@@ -271,7 +269,6 @@ public class MetricsController {
|
|||||||
private List<EndpointCount> getUniqueUserCounts(String method) {
|
private List<EndpointCount> getUniqueUserCounts(String method) {
|
||||||
log.info("Getting unique user counts for method: {}", method);
|
log.info("Getting unique user counts for method: {}", method);
|
||||||
Map<String, Set<String>> uniqueUsers = new HashMap<>();
|
Map<String, Set<String>> uniqueUsers = new HashMap<>();
|
||||||
|
|
||||||
meterRegistry
|
meterRegistry
|
||||||
.find("http.requests")
|
.find("http.requests")
|
||||||
.tag("method", method)
|
.tag("method", method)
|
||||||
@@ -284,19 +281,37 @@ public class MetricsController {
|
|||||||
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
|
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
List<EndpointCount> result =
|
List<EndpointCount> result =
|
||||||
uniqueUsers.entrySet().stream()
|
uniqueUsers.entrySet().stream()
|
||||||
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
|
||||||
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
log.info("Found {} endpoints with unique user counts", result.size());
|
log.info("Found {} endpoints with unique user counts", result.size());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/uptime")
|
||||||
|
public ResponseEntity<?> getUptime() {
|
||||||
|
if (!metricsEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
Duration uptime = Duration.between(StartupApplicationListener.startTime, now);
|
||||||
|
return ResponseEntity.ok(formatDuration(uptime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDuration(Duration duration) {
|
||||||
|
long days = duration.toDays();
|
||||||
|
long hours = duration.toHoursPart();
|
||||||
|
long minutes = duration.toMinutesPart();
|
||||||
|
long seconds = duration.toSecondsPart();
|
||||||
|
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
public static class EndpointCount {
|
public static class EndpointCount {
|
||||||
|
|
||||||
private String endpoint;
|
private String endpoint;
|
||||||
|
|
||||||
private double count;
|
private double count;
|
||||||
|
|
||||||
public EndpointCount(String endpoint, double count) {
|
public EndpointCount(String endpoint, double count) {
|
||||||
@@ -320,23 +335,4 @@ public class MetricsController {
|
|||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/uptime")
|
|
||||||
public ResponseEntity<?> getUptime() {
|
|
||||||
if (!metricsEnabled) {
|
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
Duration uptime = Duration.between(StartupApplicationListener.startTime, now);
|
|
||||||
return ResponseEntity.ok(formatDuration(uptime));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatDuration(Duration duration) {
|
|
||||||
long days = duration.toDays();
|
|
||||||
long hours = duration.toHoursPart();
|
|
||||||
long minutes = duration.toMinutesPart();
|
|
||||||
long seconds = duration.toSecondsPart();
|
|
||||||
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -22,7 +21,11 @@ import stirling.software.SPDF.utils.CheckProgramInstall;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class OtherWebController {
|
public class OtherWebController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public OtherWebController(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/compress-pdf")
|
@GetMapping("/compress-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
|
|||||||
@@ -18,10 +18,16 @@ import stirling.software.SPDF.service.SignatureService;
|
|||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
public class SignatureController {
|
public class SignatureController {
|
||||||
|
|
||||||
@Autowired private SignatureService signatureService;
|
private final SignatureService signatureService;
|
||||||
|
|
||||||
@Autowired(required = false)
|
private final UserServiceInterface userService;
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
public SignatureController(
|
||||||
|
SignatureService signatureService,
|
||||||
|
@Autowired(required = false) UserServiceInterface userService) {
|
||||||
|
this.signatureService = signatureService;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/sign/{fileName}")
|
@GetMapping("/sign/{fileName}")
|
||||||
public ResponseEntity<byte[]> getSignature(@PathVariable(name = "fileName") String fileName)
|
public ResponseEntity<byte[]> getSignature(@PathVariable(name = "fileName") String fileName)
|
||||||
@@ -30,15 +36,14 @@ public class SignatureController {
|
|||||||
if (userService != null) {
|
if (userService != null) {
|
||||||
username = userService.getCurrentUsername();
|
username = userService.getCurrentUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify access permission
|
// Verify access permission
|
||||||
if (!signatureService.hasAccessToFile(username, fileName)) {
|
if (!signatureService.hasAccessToFile(username, fileName)) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);
|
byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.IMAGE_JPEG) // Adjust based on file type
|
.contentType( // Adjust based on file type
|
||||||
|
MediaType.IMAGE_JPEG)
|
||||||
.body(imageBytes);
|
.body(imageBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,23 @@ public class ApplicationProperties {
|
|||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
return saml2.getEnabled() || oauth2.getEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUserPass() {
|
||||||
|
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|
||||||
|
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOauth2Activ() {
|
||||||
|
return (oauth2 != null
|
||||||
|
&& oauth2.getEnabled()
|
||||||
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSaml2Activ() {
|
||||||
|
return (saml2 != null
|
||||||
|
&& saml2.getEnabled()
|
||||||
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
public enum LoginMethods {
|
public enum LoginMethods {
|
||||||
ALL("all"),
|
ALL("all"),
|
||||||
NORMAL("normal"),
|
NORMAL("normal"),
|
||||||
@@ -97,23 +114,6 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserPass() {
|
|
||||||
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|
|
||||||
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOauth2Activ() {
|
|
||||||
return (oauth2 != null
|
|
||||||
&& oauth2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSaml2Activ() {
|
|
||||||
return (saml2 != null
|
|
||||||
&& saml2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class InitialLogin {
|
public static class InitialLogin {
|
||||||
private String username;
|
private String username;
|
||||||
@@ -122,18 +122,19 @@ public class ApplicationProperties {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ToString
|
||||||
public static class SAML2 {
|
public static class SAML2 {
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private Boolean autoCreateUser = false;
|
private Boolean autoCreateUser = false;
|
||||||
private Boolean blockRegistration = false;
|
private Boolean blockRegistration = false;
|
||||||
private String registrationId = "stirling";
|
private String registrationId = "stirling";
|
||||||
private String idpMetadataUri;
|
@ToString.Exclude private String idpMetadataUri;
|
||||||
private String idpSingleLogoutUrl;
|
private String idpSingleLogoutUrl;
|
||||||
private String idpSingleLoginUrl;
|
private String idpSingleLoginUrl;
|
||||||
private String idpIssuer;
|
private String idpIssuer;
|
||||||
private String idpCert;
|
private String idpCert;
|
||||||
private String privateKey;
|
@ToString.Exclude private String privateKey;
|
||||||
private String spCert;
|
@ToString.Exclude private String spCert;
|
||||||
|
|
||||||
public InputStream getIdpMetadataUri() throws IOException {
|
public InputStream getIdpMetadataUri() throws IOException {
|
||||||
if (idpMetadataUri.startsWith("classpath:")) {
|
if (idpMetadataUri.startsWith("classpath:")) {
|
||||||
|
|||||||
@@ -2,14 +2,7 @@ package stirling.software.SPDF.model;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "authorities")
|
@Table(name = "authorities")
|
||||||
@@ -17,14 +10,6 @@ public class Authority implements Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public Authority() {}
|
|
||||||
|
|
||||||
public Authority(String authority, User user) {
|
|
||||||
this.authority = authority;
|
|
||||||
this.user = user;
|
|
||||||
user.getAuthorities().add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
@@ -36,6 +21,14 @@ public class Authority implements Serializable {
|
|||||||
@JoinColumn(name = "user_id")
|
@JoinColumn(name = "user_id")
|
||||||
private User user;
|
private User user;
|
||||||
|
|
||||||
|
public Authority() {}
|
||||||
|
|
||||||
|
public Authority(String authority, User user) {
|
||||||
|
this.authority = authority;
|
||||||
|
this.user = user;
|
||||||
|
user.getAuthorities().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,22 +40,6 @@ public enum Role {
|
|||||||
this.roleName = roleName;
|
this.roleName = roleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRoleId() {
|
|
||||||
return roleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getApiCallsPerDay() {
|
|
||||||
return apiCallsPerDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWebCallsPerDay() {
|
|
||||||
return webCallsPerDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRoleName() {
|
|
||||||
return roleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRoleNameByRoleId(String roleId) {
|
public static String getRoleNameByRoleId(String roleId) {
|
||||||
// Using the fromString method to get the Role enum based on the roleId
|
// Using the fromString method to get the Role enum based on the roleId
|
||||||
Role role = fromString(roleId);
|
Role role = fromString(roleId);
|
||||||
@@ -81,4 +65,20 @@ public enum Role {
|
|||||||
}
|
}
|
||||||
throw new IllegalArgumentException("No Role defined for id: " + roleId);
|
throw new IllegalArgumentException("No Role defined for id: " + roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRoleId() {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getApiCallsPerDay() {
|
||||||
|
return apiCallsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWebCallsPerDay() {
|
||||||
|
return webCallsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleName() {
|
||||||
|
return roleName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,14 +111,14 @@ public class User implements Serializable {
|
|||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationType(AuthenticationType authenticationType) {
|
|
||||||
this.authenticationType = authenticationType.toString().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthenticationType() {
|
public String getAuthenticationType() {
|
||||||
return authenticationType;
|
return authenticationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationType(AuthenticationType authenticationType) {
|
||||||
|
this.authenticationType = authenticationType.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Authority> getAuthorities() {
|
public Set<Authority> getAuthorities() {
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user