Compare commits
97 Commits
Frooodle/f
...
saml2-sso-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92c0ddf63b | ||
|
|
7297e9e62d | ||
|
|
f8944fd2a9 | ||
|
|
3fd44fe7af | ||
|
|
4c9c9b5cbe | ||
|
|
e660237e28 | ||
|
|
83e93688ee | ||
|
|
dedfabd630 | ||
|
|
3b5b7772a9 | ||
|
|
2a6b4ca87f | ||
|
|
e325943f16 | ||
|
|
48aae48f0e | ||
|
|
494bc2c09f | ||
|
|
45e4c15d2d | ||
|
|
22a58ad0c3 | ||
|
|
c59d3ff3e0 | ||
|
|
bb37ba1f30 | ||
|
|
5832147b30 | ||
|
|
092b4cc5cb | ||
|
|
86bb37aa7a | ||
|
|
20dc2f60cd | ||
|
|
da988e8127 | ||
|
|
b8115531e2 | ||
|
|
9b96367496 | ||
|
|
3ded6de576 | ||
|
|
1c6e5df77d | ||
|
|
df901db1f8 | ||
|
|
b46ccdde44 | ||
|
|
fde1f626eb | ||
|
|
f47ed3b42e | ||
|
|
a81856d83b | ||
|
|
4fa1b4adb0 | ||
|
|
03158b05e4 | ||
|
|
28c55ca80c | ||
|
|
d6e9e8b20b | ||
|
|
0f43062cc1 | ||
|
|
f72e5c8bca | ||
|
|
936f36f171 | ||
|
|
dab230e3f8 | ||
|
|
0d3ac8bebe | ||
|
|
6ca14edaf1 | ||
|
|
f9677b1fe8 | ||
|
|
04a6ebf515 | ||
|
|
262e2ed47a | ||
|
|
4436759e12 | ||
|
|
6e1a5d2ea0 | ||
|
|
35490f6ff7 | ||
|
|
9f63b0b115 | ||
|
|
c2a8771c66 | ||
|
|
f87801323c | ||
|
|
bed6227bbe | ||
|
|
24f99fce31 | ||
|
|
ba2311b3e5 | ||
|
|
688e01d70d | ||
|
|
0014560a96 | ||
|
|
cbf1c3a59b | ||
|
|
b13b925bf0 | ||
|
|
87925ac618 | ||
|
|
c6c33d611a | ||
|
|
d389b5e2f3 | ||
|
|
de97492f39 | ||
|
|
9661e94092 | ||
|
|
2cfb553320 | ||
|
|
909a3347a0 | ||
|
|
de4144a1a4 | ||
|
|
bb1c859e0d | ||
|
|
b2862a3fc4 | ||
|
|
8788a7ee34 | ||
|
|
8c01425eee | ||
|
|
4d47e535b5 | ||
|
|
1fb78c3124 | ||
|
|
6a9dd4ea95 | ||
|
|
0773b8e11b | ||
|
|
184d89c44a | ||
|
|
df2e9bfc6e | ||
|
|
0045fe852b | ||
|
|
7d46d61d9e | ||
|
|
f8404ce9e9 | ||
|
|
7fad973a77 | ||
|
|
6410a99cf3 | ||
|
|
62e7c7e073 | ||
|
|
1d29a500b3 | ||
|
|
08b085c763 | ||
|
|
f256e8f029 | ||
|
|
0ad8c635ad | ||
|
|
12ff0ecac2 | ||
|
|
291bad4a2a | ||
|
|
c6ee96512a | ||
|
|
db563c765d | ||
|
|
6f52189ed2 | ||
|
|
580313151b | ||
|
|
765289c89e | ||
|
|
3d8686211d | ||
|
|
0a98e3bde3 | ||
|
|
78211d09c5 | ||
|
|
82219dd899 | ||
|
|
3c04486348 |
@@ -1,5 +1,5 @@
|
||||
# Main stage
|
||||
FROM alpine:3.20.2
|
||||
FROM alpine:3.20.3
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
|
||||
./gradlew clean build
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.20.2
|
||||
FROM alpine:3.20.3
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use alpine
|
||||
FROM alpine:3.20.2
|
||||
FROM alpine:3.20.3
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -15,6 +15,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
# Copy necessary files
|
||||
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
@@ -33,11 +34,11 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
su-exec \
|
||||
openjdk21-jre && \
|
||||
# User permissions
|
||||
mkdir /configs /logs /customFiles && \
|
||||
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
# Set environment variables
|
||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||
|
||||
@@ -257,9 +257,11 @@ To override the default configuration, you can add the following to `/.git/Stirl
|
||||
|
||||
```bash
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
host: 0.0.0.0 # Not working - use instead address
|
||||
address: 0.0.0.0
|
||||
port: 3000
|
||||
```
|
||||
'-Djava.net.preferIPv4Stack=true' --> To force ipv4 only in the java starting command
|
||||
|
||||
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
||||
|
||||
|
||||
66
README.md
66
README.md
@@ -100,6 +100,8 @@ Demo of the app is available [here](https://stirlingpdf.io).
|
||||
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
||||
|
||||
## How to use
|
||||
### Windows
|
||||
For windows users download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe)
|
||||
|
||||
### Locally
|
||||
|
||||
@@ -170,42 +172,42 @@ Stirling PDF currently supports 38!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
|
||||
27
build.gradle
27
build.gradle
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot" version "3.3.3"
|
||||
id "org.springframework.boot" version "3.3.4"
|
||||
id "io.spring.dependency-management" version "1.1.6"
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||
id "io.swagger.swaggerhub" version "1.3.2"
|
||||
@@ -13,7 +13,7 @@ plugins {
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
ext {
|
||||
springBootVersion = "3.3.3"
|
||||
springBootVersion = "3.3.4"
|
||||
pdfboxVersion = "3.0.3"
|
||||
logbackVersion = "1.5.7"
|
||||
imageioVersion = "3.11.0"
|
||||
@@ -22,7 +22,7 @@ ext {
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.28.3"
|
||||
version = "0.29.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
@@ -32,6 +32,12 @@ java {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven {
|
||||
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://build.shibboleth.net/maven/releases/"
|
||||
}
|
||||
}
|
||||
|
||||
licenseReport {
|
||||
@@ -115,7 +121,7 @@ configurations.all {
|
||||
}
|
||||
dependencies {
|
||||
//security updates
|
||||
implementation "org.springframework:spring-webmvc:6.1.9"
|
||||
implementation "org.springframework:spring-webmvc:6.1.13"
|
||||
|
||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
||||
|
||||
@@ -127,6 +133,7 @@ dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||
implementation 'com.posthog.java:posthog:1.1.1'
|
||||
|
||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||
@@ -134,6 +141,8 @@ dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||
|
||||
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3'
|
||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||
//2.2.x requires rebuild of DB file.. need migration path
|
||||
runtimeOnly "com.h2database:h2:2.1.214"
|
||||
// implementation "com.h2database:h2:2.2.224"
|
||||
@@ -162,7 +171,7 @@ dependencies {
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
||||
|
||||
implementation "commons-io:commons-io:2.16.1"
|
||||
implementation "commons-io:commons-io:2.17.0"
|
||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||
//general PDF
|
||||
|
||||
@@ -184,14 +193,14 @@ dependencies {
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
implementation "io.micrometer:micrometer-core:1.13.3"
|
||||
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark:0.22.0"
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
|
||||
implementation "org.commonmark:commonmark:0.23.0"
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||
implementation "com.fathzer:javaluator:3.0.4"
|
||||
implementation "com.fathzer:javaluator:3.0.5"
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.28.3
|
||||
appVersion: 0.29.0
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
|
||||
@@ -123,7 +123,7 @@ Feature: API Validation
|
||||
| odt | .odt |
|
||||
| doc | .doc |
|
||||
|
||||
@ocr
|
||||
@ocr @pdfa1
|
||||
Scenario: PDFA
|
||||
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
@@ -134,7 +134,7 @@ Feature: API Validation
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@ocr
|
||||
@ocr @pdfa2
|
||||
Scenario: PDFA1
|
||||
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Ultra-Lite-Security
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Ultra-Lite
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF
|
||||
|
||||
10
scripts/replace_translation_line.sh
Normal file
10
scripts/replace_translation_line.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
translation_key="pdfToPDFA.credit"
|
||||
old_value="OCRmyPDF"
|
||||
new_value="ghostscript"
|
||||
|
||||
for file in ../src/main/resources/messages_*.properties; do
|
||||
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"
|
||||
echo "Updated $file"
|
||||
done
|
||||
26
src/main/java/stirling/software/SPDF/EE/EEAppConfig.java
Normal file
26
src/main/java/stirling/software/SPDF/EE/EEAppConfig.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
@Lazy
|
||||
public class EEAppConfig {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class);
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||
|
||||
@Bean(name = "runningEE")
|
||||
public boolean runningEnterpriseEdition() {
|
||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.posthog.java.shaded.org.json.JSONObject;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class KeygenLicenseVerifier {
|
||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||
private static final String PRODUCT_ID = "f9bb2423-62c9-4d39-8def-4fdc5aca751e";
|
||||
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 23:26:20.344 [scheduling-1] INFO s.s.SPDF.EE.KeygenLicenseVerifier -
|
||||
// validateLicenseResponse body:
|
||||
// {"data":{"id":"808ed3c9-584b-46dd-8a80-c9217ef70915","type":"licenses","attributes":{"name":"userCounTest","key":"A7EW-KUPF-PRML-RRVL-HLMP-7THR-F7KE-XF4C","expiry":"2024-10-31T21:39:49.271Z","status":"ACTIVE","uses":0,"suspended":false,"scheme":null,"encrypted":false,"strict":true,"floating":true,"protected":true,"version":null,"maxMachines":1,"maxProcesses":null,"maxUsers":null,"maxCores":null,"maxUses":null,"requireHeartbeat":false,"requireCheckIn":false,"lastValidated":"2024-10-01T22:26:18.121Z","lastCheckIn":null,"nextCheckIn":null,"lastCheckOut":null,"metadata":{"users":10},"created":"2024-10-01T21:39:49.268Z","updated":"2024-10-01T21:39:49.268Z"},"relationships":{"account":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372"},"data":{"type":"accounts","id":"e5430f69-e834-4ae4-befd-b602aae5f372"}},"environment":{"links":{"related":null},"data":null},"product":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/product"},"data":{"type":"products","id":"f9bb2423-62c9-4d39-8def-4fdc5aca751e"}},"policy":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/policy"},"data":{"type":"policies","id":"04caef06-9ac2-4084-bf3c-bca4a0d29143"}},"group":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/group"},"data":null},"owner":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/owner"},"data":null},"users":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/users"},"meta":{"count":0}},"machines":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/machines"},"meta":{"cores":0,"count":0}},"tokens":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/tokens"}},"entitlements":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/entitlements"}}},"links":{"self":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915"}},"meta":{"ts":"2024-10-01T22:26:18.124Z","valid":false,"detail":"fingerprint is not activated (has no associated machines)","code":"NO_MACHINES","scope":{"fingerprint":"example-fingerprint"}}}
|
||||
|
||||
public boolean verifyLicense(String licenseKey) {
|
||||
try {
|
||||
log.info("Checking license key");
|
||||
String machineFingerprint = generateMachineFingerprint();
|
||||
|
||||
// First, try to validate the license
|
||||
JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||
if (validationResponse != null) {
|
||||
boolean isValid = validationResponse.path("meta").path("valid").asBoolean();
|
||||
String licenseId = validationResponse.path("data").path("id").asText();
|
||||
if (!isValid) {
|
||||
String code = validationResponse.path("meta").path("code").asText();
|
||||
log.debug(code);
|
||||
if ("NO_MACHINE".equals(code)
|
||||
|| "NO_MACHINES".equals(code)
|
||||
|| "FINGERPRINT_SCOPE_MISMATCH".equals(code)) {
|
||||
log.info(
|
||||
"License not activated for this machine. Attempting to activate...");
|
||||
boolean activated =
|
||||
activateMachine(licenseKey, licenseId, machineFingerprint);
|
||||
if (activated) {
|
||||
// Revalidate after activation
|
||||
validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||
isValid =
|
||||
validationResponse != null
|
||||
&& validationResponse
|
||||
.path("meta")
|
||||
.path("valid")
|
||||
.asBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Error verifying license: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode validateLicense(String licenseKey, String machineFingerprint)
|
||||
throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String requestBody =
|
||||
String.format(
|
||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||
licenseKey, machineFingerprint);
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(
|
||||
URI.create(
|
||||
BASE_URL
|
||||
+ "/"
|
||||
+ ACCOUNT_ID
|
||||
+ "/licenses/actions/validate-key"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
// .header("Authorization", "License " + licenseKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.info(" validateLicenseResponse body: " + response.body());
|
||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||
if (response.statusCode() == 200) {
|
||||
|
||||
JsonNode metaNode = jsonResponse.path("meta");
|
||||
boolean isValid = metaNode.path("valid").asBoolean();
|
||||
|
||||
String detail = metaNode.path("detail").asText();
|
||||
String code = metaNode.path("code").asText();
|
||||
|
||||
log.debug("License validity: " + isValid);
|
||||
log.debug("Validation detail: " + detail);
|
||||
log.debug("Validation code: " + code);
|
||||
|
||||
} else {
|
||||
log.error("Error validating license. Status code: " + response.statusCode());
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
private static boolean activateMachine(
|
||||
String licenseKey, String licenseId, String machineFingerprint) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
String hostname;
|
||||
try {
|
||||
hostname = java.net.InetAddress.getLocalHost().getHostName();
|
||||
} catch (Exception e) {
|
||||
hostname = "Unknown";
|
||||
}
|
||||
|
||||
JSONObject body =
|
||||
new JSONObject()
|
||||
.put(
|
||||
"data",
|
||||
new JSONObject()
|
||||
.put("type", "machines")
|
||||
.put(
|
||||
"attributes",
|
||||
new JSONObject()
|
||||
.put("fingerprint", machineFingerprint)
|
||||
.put(
|
||||
"platform",
|
||||
System.getProperty(
|
||||
"os.name")) // Added
|
||||
// platform
|
||||
// parameter
|
||||
.put(
|
||||
"name",
|
||||
hostname)) // Added name parameter
|
||||
.put(
|
||||
"relationships",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"license",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"data",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"type",
|
||||
"licenses")
|
||||
.put(
|
||||
"id",
|
||||
licenseId)))));
|
||||
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header(
|
||||
"Authorization",
|
||||
"License " + licenseKey) // Keep the license key authentication
|
||||
.POST(
|
||||
HttpRequest.BodyPublishers.ofString(
|
||||
body.toString())) // Send the JSON body
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.debug("activateMachine Response body: " + response.body());
|
||||
if (response.statusCode() == 201) {
|
||||
log.info("Machine activated successfully");
|
||||
return true;
|
||||
} else {
|
||||
log.error(
|
||||
"Error activating machine. Status code: {}, error: {}",
|
||||
response.statusCode(),
|
||||
response.body());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateMachineFingerprint() {
|
||||
return GeneralUtils.generateMachineFingerprint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LicenseKeyChecker {
|
||||
|
||||
private final KeygenLicenseVerifier licenseService;
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
private boolean enterpriseEnbaledResult = false;
|
||||
|
||||
// Inject your license service or configuration
|
||||
@Autowired
|
||||
public LicenseKeyChecker(
|
||||
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
||||
this.licenseService = licenseService;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
|
||||
public void checkLicensePeriodically() {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
private void checkLicense() {
|
||||
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||
enterpriseEnbaledResult = false;
|
||||
} else {
|
||||
enterpriseEnbaledResult =
|
||||
licenseService.verifyLicense(
|
||||
applicationProperties.getEnterpriseEdition().getKey());
|
||||
if (enterpriseEnbaledResult) {
|
||||
log.info("License key is valid.");
|
||||
} else {
|
||||
log.info("License key is invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLicenseKey(String newKey) throws IOException {
|
||||
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||
GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false);
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
public boolean getEnterpriseEnabledResult() {
|
||||
return enterpriseEnbaledResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package stirling.software.SPDF.Factories;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
import stirling.software.SPDF.utils.misc.CustomColorReplaceStrategy;
|
||||
import stirling.software.SPDF.utils.misc.InvertFullColorStrategy;
|
||||
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
|
||||
|
||||
@Component
|
||||
public class ReplaceAndInvertColorFactory {
|
||||
|
||||
public ReplaceAndInvertColorStrategy replaceAndInvert(
|
||||
MultipartFile file,
|
||||
ReplaceAndInvert replaceAndInvertOption,
|
||||
HighContrastColorCombination highContrastColorCombination,
|
||||
String backGroundColor,
|
||||
String textColor) {
|
||||
|
||||
if (replaceAndInvertOption == ReplaceAndInvert.CUSTOM_COLOR
|
||||
|| replaceAndInvertOption == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
|
||||
|
||||
return new CustomColorReplaceStrategy(
|
||||
file,
|
||||
replaceAndInvertOption,
|
||||
textColor,
|
||||
backGroundColor,
|
||||
highContrastColorCombination);
|
||||
|
||||
} else if (replaceAndInvertOption == ReplaceAndInvert.FULL_INVERSION) {
|
||||
|
||||
return new InvertFullColorStrategy(file, replaceAndInvertOption);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.pixee.security.SystemCommand;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class LibreOfficeListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
||||
@@ -31,7 +34,7 @@ public class LibreOfficeListener {
|
||||
private LibreOfficeListener() {}
|
||||
|
||||
private boolean isListenerRunning() {
|
||||
System.out.println("waiting for listener to start");
|
||||
log.info("waiting for listener to start");
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(
|
||||
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -30,15 +31,36 @@ public class SPdfApplication {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||
|
||||
@Autowired private Environment env;
|
||||
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
private static String serverPortStatic;
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
public void setServerPortStatic(String port) {
|
||||
SPdfApplication.serverPortStatic = port;
|
||||
if (port.equalsIgnoreCase("auto")) {
|
||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||
SPdfApplication.serverPortStatic =
|
||||
"0"; // This will let Spring Boot assign an available port
|
||||
} else {
|
||||
SPdfApplication.serverPortStatic = port;
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||
private static String findAvailablePort(int startPort) {
|
||||
int port = startPort;
|
||||
while (!isPortAvailable(port)) {
|
||||
port++;
|
||||
}
|
||||
return String.valueOf(port);
|
||||
}
|
||||
|
||||
private static boolean isPortAvailable(int port) {
|
||||
try (ServerSocket socket = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@@ -48,13 +70,17 @@ public class SPdfApplication {
|
||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||
if (browserOpen) {
|
||||
try {
|
||||
String url = "http://localhost:" + getNonStaticPort();
|
||||
String url = "http://localhost:" + getStaticPort();
|
||||
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
if (os.contains("win")) {
|
||||
// For Windows
|
||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||
} else if (os.contains("mac")) {
|
||||
rt.exec("open " + url);
|
||||
} else if (os.contains("nix") || os.contains("nux")) {
|
||||
rt.exec("xdg-open " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error opening browser: {}", e.getMessage());
|
||||
@@ -70,15 +96,13 @@ public class SPdfApplication {
|
||||
app.addInitializers(new ConfigInitializer());
|
||||
Map<String, String> propertyFiles = new HashMap<>();
|
||||
|
||||
// stirling pdf settings file
|
||||
// External config files
|
||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||
} else {
|
||||
logger.warn(
|
||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
|
||||
}
|
||||
|
||||
// custom javs settings file
|
||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||
String existingLocation =
|
||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||
@@ -101,19 +125,14 @@ public class SPdfApplication {
|
||||
|
||||
app.run(args);
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
|
||||
// Ensure directories are created
|
||||
try {
|
||||
Files.createDirectories(Path.of("customFiles/static/"));
|
||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
||||
} catch (Exception e) {
|
||||
logger.error("Error creating directories: {}", e.getMessage());
|
||||
}
|
||||
|
||||
printStartupLogs();
|
||||
}
|
||||
|
||||
|
||||
@@ -135,4 +135,52 @@ public class AppConfig {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean(name = "termsAndConditions")
|
||||
public String termsAndConditions() {
|
||||
return applicationProperties.getLegal().getTermsAndConditions();
|
||||
}
|
||||
|
||||
@Bean(name = "privacyPolicy")
|
||||
public String privacyPolicy() {
|
||||
return applicationProperties.getLegal().getPrivacyPolicy();
|
||||
}
|
||||
|
||||
@Bean(name = "cookiePolicy")
|
||||
public String cookiePolicy() {
|
||||
return applicationProperties.getLegal().getCookiePolicy();
|
||||
}
|
||||
|
||||
@Bean(name = "impressum")
|
||||
public String impressum() {
|
||||
return applicationProperties.getLegal().getImpressum();
|
||||
}
|
||||
|
||||
@Bean(name = "accessibilityStatement")
|
||||
public String accessibilityStatement() {
|
||||
return applicationProperties.getLegal().getAccessibilityStatement();
|
||||
}
|
||||
|
||||
@Bean(name = "analyticsPrompt")
|
||||
public boolean analyticsPrompt() {
|
||||
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||
}
|
||||
|
||||
@Bean(name = "analyticsEnabled")
|
||||
public boolean analyticsEnabled() {
|
||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||
&& Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics());
|
||||
}
|
||||
|
||||
@Bean(name = "StirlingPDFLabel")
|
||||
public String stirlingPDFLabel() {
|
||||
return "Stirling-PDF" + " v" + appVersion();
|
||||
}
|
||||
|
||||
@Bean(name = "UUID")
|
||||
public String uuid() {
|
||||
return applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
@@ -18,7 +19,7 @@ class AppUpdateService {
|
||||
@Bean(name = "shouldShow")
|
||||
@Scope("request")
|
||||
public boolean shouldShow() {
|
||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
||||
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
|
||||
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
|
||||
return showUpdate && showAdminResult;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public class InitialSetup {
|
||||
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void initUUIDKey() throws IOException {
|
||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||
uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
||||
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
public class Beans implements WebMvcConfigurer {
|
||||
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@@ -13,6 +13,8 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Component
|
||||
public class MetricsFilter extends OncePerRequestFilter {
|
||||
@@ -30,32 +32,17 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||
throws ServletException, IOException {
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||
// Ignore static resources
|
||||
if (!(uri.startsWith("/js")
|
||||
|| uri.startsWith("/v1/api-docs")
|
||||
|| uri.endsWith("robots.txt")
|
||||
|| uri.startsWith("/images")
|
||||
|| uri.endsWith(".png")
|
||||
|| uri.endsWith(".ico")
|
||||
|| uri.endsWith(".css")
|
||||
|| uri.endsWith(".map")
|
||||
|| uri.endsWith(".svg")
|
||||
|| uri.endsWith(".js")
|
||||
|| uri.contains("swagger")
|
||||
|| uri.startsWith("/api/v1/info")
|
||||
|| uri.startsWith("/site.webmanifest")
|
||||
|| uri.startsWith("/fonts")
|
||||
|| uri.startsWith("/pdfjs"))) {
|
||||
|
||||
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
||||
HttpSession session = request.getSession(false);
|
||||
String sessionId = (session != null) ? session.getId() : "no-session";
|
||||
Counter counter =
|
||||
Counter.builder("http.requests")
|
||||
.tag("uri", uri)
|
||||
.tag("session", sessionId)
|
||||
.tag("method", request.getMethod())
|
||||
.tag("uri", uri)
|
||||
.register(meterRegistry);
|
||||
|
||||
counter.increment();
|
||||
// System.out.println("Counted");
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.posthog.java.PostHog;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
@Configuration
|
||||
public class PostHogConfig {
|
||||
|
||||
@Value("${posthog.api.key}")
|
||||
private String posthogApiKey;
|
||||
|
||||
@Value("${posthog.host}")
|
||||
private String posthogHost;
|
||||
|
||||
private PostHog postHogClient;
|
||||
|
||||
@Bean
|
||||
public PostHog postHogClient() {
|
||||
postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build();
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdownPostHog() {
|
||||
if (postHogClient != null) {
|
||||
postHogClient.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.io.IOException;
|
||||
//
|
||||
// import org.springframework.beans.factory.annotation.Autowired;
|
||||
// import org.springframework.stereotype.Component;
|
||||
// import org.springframework.web.filter.OncePerRequestFilter;
|
||||
//
|
||||
// import jakarta.servlet.FilterChain;
|
||||
// import jakarta.servlet.ServletException;
|
||||
// import jakarta.servlet.http.HttpServletRequest;
|
||||
// import jakarta.servlet.http.HttpServletResponse;
|
||||
// import jakarta.servlet.http.HttpSession;
|
||||
// import lombok.extern.slf4j.Slf4j;
|
||||
// import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
//
|
||||
//// @Component
|
||||
// @Slf4j
|
||||
// public class FingerprintBasedSessionFilter extends OncePerRequestFilter {
|
||||
// private final FingerprintGenerator fingerprintGenerator;
|
||||
// private final FingerprintBasedSessionManager sessionManager;
|
||||
//
|
||||
// @Autowired
|
||||
// public FingerprintBasedSessionFilter(
|
||||
// FingerprintGenerator fingerprintGenerator,
|
||||
// FingerprintBasedSessionManager sessionManager) {
|
||||
// this.fingerprintGenerator = fingerprintGenerator;
|
||||
// this.sessionManager = sessionManager;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void doFilterInternal(
|
||||
// HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
// throws ServletException, IOException {
|
||||
//
|
||||
// if (RequestUriUtils.isStaticResource(request.getContextPath(), request.getRequestURI())) {
|
||||
// filterChain.doFilter(request, response);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// String fingerprint = fingerprintGenerator.generateFingerprint(request);
|
||||
// log.debug("Generated fingerprint for request: {}", fingerprint);
|
||||
//
|
||||
// HttpSession session = request.getSession();
|
||||
// boolean isNewSession = session.isNew();
|
||||
// String sessionId = session.getId();
|
||||
//
|
||||
// if (isNewSession) {
|
||||
// log.info("New session created: {}", sessionId);
|
||||
// }
|
||||
//
|
||||
// if (!sessionManager.isFingerPrintAllowed(fingerprint)) {
|
||||
// log.info("Blocked fingerprint detected, redirecting: {}", fingerprint);
|
||||
// response.sendRedirect(request.getContextPath() + "/too-many-requests");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// session.setAttribute("userFingerprint", fingerprint);
|
||||
// session.setAttribute(
|
||||
// FingerprintBasedSessionManager.STARTUP_TIMESTAMP,
|
||||
// FingerprintBasedSessionManager.APP_STARTUP_TIME);
|
||||
//
|
||||
// sessionManager.registerFingerprint(fingerprint, sessionId);
|
||||
//
|
||||
// log.debug("Proceeding with request: {}", request.getRequestURI());
|
||||
// filterChain.doFilter(request, response);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,134 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.util.Iterator;
|
||||
// import java.util.Map;
|
||||
// import java.util.concurrent.ConcurrentHashMap;
|
||||
// import java.util.concurrent.TimeUnit;
|
||||
//
|
||||
// import org.springframework.scheduling.annotation.Scheduled;
|
||||
// import org.springframework.stereotype.Component;
|
||||
//
|
||||
// import jakarta.servlet.http.HttpSession;
|
||||
// import jakarta.servlet.http.HttpSessionAttributeListener;
|
||||
// import jakarta.servlet.http.HttpSessionEvent;
|
||||
// import jakarta.servlet.http.HttpSessionListener;
|
||||
// import lombok.AllArgsConstructor;
|
||||
// import lombok.Data;
|
||||
// import lombok.extern.slf4j.Slf4j;
|
||||
//
|
||||
// @Slf4j
|
||||
// @Component
|
||||
// public class FingerprintBasedSessionManager
|
||||
// implements HttpSessionListener, HttpSessionAttributeListener {
|
||||
// private static final ConcurrentHashMap<String, FingerprintInfo> activeFingerprints =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// // To be reduced in later version to 8~
|
||||
// private static final int MAX_ACTIVE_FINGERPRINTS = 30;
|
||||
//
|
||||
// static final String STARTUP_TIMESTAMP = "appStartupTimestamp";
|
||||
// static final long APP_STARTUP_TIME = System.currentTimeMillis();
|
||||
// private static final long FINGERPRINT_EXPIRATION = TimeUnit.MINUTES.toMillis(30);
|
||||
//
|
||||
// @Override
|
||||
// public void sessionCreated(HttpSessionEvent se) {
|
||||
// HttpSession session = se.getSession();
|
||||
// String sessionId = session.getId();
|
||||
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||
//
|
||||
// if (fingerprint == null) {
|
||||
// log.warn("Session created without fingerprint: {}", sessionId);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// synchronized (activeFingerprints) {
|
||||
// if (activeFingerprints.size() >= MAX_ACTIVE_FINGERPRINTS
|
||||
// && !activeFingerprints.containsKey(fingerprint)) {
|
||||
// log.info("Max fingerprints reached. Marking session as blocked: {}", sessionId);
|
||||
// session.setAttribute("blocked", true);
|
||||
// } else {
|
||||
// activeFingerprints.put(
|
||||
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||
// log.info(
|
||||
// "New fingerprint registered: {}. Total active fingerprints: {}",
|
||||
// fingerprint,
|
||||
// activeFingerprints.size());
|
||||
// }
|
||||
// session.setAttribute(STARTUP_TIMESTAMP, APP_STARTUP_TIME);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void sessionDestroyed(HttpSessionEvent se) {
|
||||
// HttpSession session = se.getSession();
|
||||
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||
//
|
||||
// if (fingerprint != null) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.remove(fingerprint);
|
||||
// log.info(
|
||||
// "Fingerprint removed: {}. Total active fingerprints: {}",
|
||||
// fingerprint,
|
||||
// activeFingerprints.size());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public boolean isFingerPrintAllowed(String fingerprint) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// return activeFingerprints.size() < MAX_ACTIVE_FINGERPRINTS
|
||||
// || activeFingerprints.containsKey(fingerprint);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void registerFingerprint(String fingerprint, String sessionId) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.put(
|
||||
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void unregisterFingerprint(String fingerprint) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.remove(fingerprint);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Scheduled(fixedRate = 1800000) // Run every 30 mins
|
||||
// public void cleanupStaleFingerprints() {
|
||||
// log.info("Starting cleanup of stale fingerprints");
|
||||
// long now = System.currentTimeMillis();
|
||||
// int removedCount = 0;
|
||||
//
|
||||
// synchronized (activeFingerprints) {
|
||||
// Iterator<Map.Entry<String, FingerprintInfo>> iterator =
|
||||
// activeFingerprints.entrySet().iterator();
|
||||
// while (iterator.hasNext()) {
|
||||
// Map.Entry<String, FingerprintInfo> entry = iterator.next();
|
||||
// FingerprintInfo info = entry.getValue();
|
||||
//
|
||||
// if (now - info.getLastAccessTime() > FINGERPRINT_EXPIRATION) {
|
||||
// iterator.remove();
|
||||
// removedCount++;
|
||||
// log.info("Removed stale fingerprint: {}", entry.getKey());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// log.info("Cleanup complete. Removed {} stale fingerprints", removedCount);
|
||||
// }
|
||||
//
|
||||
// public void updateLastAccessTime(String fingerprint) {
|
||||
// FingerprintInfo info = activeFingerprints.get(fingerprint);
|
||||
// if (info != null) {
|
||||
// info.setLastAccessTime(System.currentTimeMillis());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Data
|
||||
// @AllArgsConstructor
|
||||
// private static class FingerprintInfo {
|
||||
// private String sessionId;
|
||||
// private long lastAccessTime;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,77 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.security.MessageDigest;
|
||||
// import java.security.NoSuchAlgorithmException;
|
||||
//
|
||||
// import org.springframework.stereotype.Component;
|
||||
//
|
||||
// import jakarta.servlet.http.HttpServletRequest;
|
||||
//
|
||||
// @Component
|
||||
// public class FingerprintGenerator {
|
||||
//
|
||||
// public String generateFingerprint(HttpServletRequest request) {
|
||||
// if (request == null) {
|
||||
// return "";
|
||||
// }
|
||||
// StringBuilder fingerprintBuilder = new StringBuilder();
|
||||
//
|
||||
// // Add IP address
|
||||
// fingerprintBuilder.append(request.getRemoteAddr());
|
||||
//
|
||||
// // Add X-Forwarded-For header if present (for clients behind proxies)
|
||||
// String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
// if (forwardedFor != null) {
|
||||
// fingerprintBuilder.append(forwardedFor);
|
||||
// }
|
||||
//
|
||||
// // Add User-Agent
|
||||
// String userAgent = request.getHeader("User-Agent");
|
||||
// if (userAgent != null) {
|
||||
// fingerprintBuilder.append(userAgent);
|
||||
// }
|
||||
//
|
||||
// // Add Accept-Language header
|
||||
// String acceptLanguage = request.getHeader("Accept-Language");
|
||||
// if (acceptLanguage != null) {
|
||||
// fingerprintBuilder.append(acceptLanguage);
|
||||
// }
|
||||
//
|
||||
// // Add Accept header
|
||||
// String accept = request.getHeader("Accept");
|
||||
// if (accept != null) {
|
||||
// fingerprintBuilder.append(accept);
|
||||
// }
|
||||
//
|
||||
// // Add Connection header
|
||||
// String connection = request.getHeader("Connection");
|
||||
// if (connection != null) {
|
||||
// fingerprintBuilder.append(connection);
|
||||
// }
|
||||
//
|
||||
// // Add server port
|
||||
// fingerprintBuilder.append(request.getServerPort());
|
||||
//
|
||||
// // Add secure flag
|
||||
// fingerprintBuilder.append(request.isSecure());
|
||||
//
|
||||
// // Generate a hash of the fingerprint
|
||||
// return generateHash(fingerprintBuilder.toString());
|
||||
// }
|
||||
//
|
||||
// private String generateHash(String input) {
|
||||
// try {
|
||||
// MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
// byte[] hash = digest.digest(input.getBytes());
|
||||
// StringBuilder hexString = new StringBuilder();
|
||||
// for (byte b : hash) {
|
||||
// String hex = Integer.toHexString(0xff & b);
|
||||
// if (hex.length() == 1) hexString.append('0');
|
||||
// hexString.append(hex);
|
||||
// }
|
||||
// return hexString.toString();
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// throw new RuntimeException("Failed to generate fingerprint hash", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.config;
|
||||
package stirling.software.SPDF.config.interfaces;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.config;
|
||||
package stirling.software.SPDF.config.interfaces;
|
||||
|
||||
public interface ShowAdminInterface {
|
||||
default boolean getShowUpdateOnlyAdmins() {
|
||||
@@ -7,7 +7,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.ShowAdminInterface;
|
||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
@@ -20,7 +20,7 @@ class AppUpdateAuthService implements ShowAdminInterface {
|
||||
|
||||
@Override
|
||||
public boolean getShowUpdateOnlyAdmins() {
|
||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
||||
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
|
||||
if (!showUpdate) {
|
||||
return showUpdate;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,9 +16,12 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
|
||||
@@ -50,6 +55,22 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
HttpSession session = request.getSession(true);
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
||||
|
||||
log.debug(
|
||||
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
||||
session.isNew(),
|
||||
creationTime,
|
||||
session.getId(),
|
||||
request.getRemoteAddr(),
|
||||
request.getHeader("User-Agent"),
|
||||
request.getHeader("Referer"),
|
||||
request.getRequestURL().toString());
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
|
||||
@@ -39,15 +34,6 @@ public class InitialSecuritySetup {
|
||||
initializeInternalApiUser();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (!isValidUUID(secretKey)) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
saveKeyToConfig(secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeAdminUser() throws IOException {
|
||||
String initialUsername =
|
||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||
@@ -89,33 +75,4 @@ public class InitialSecuritySetup {
|
||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeyToConfig(String key) throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
DumperOptions yamlOptionssettingsYml =
|
||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||
yamlOptionssettingsYml.setSplitLines(false);
|
||||
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
settingsYml
|
||||
.path("AutomaticallyGenerated.key")
|
||||
.set(key)
|
||||
.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
private boolean isValidUUID(String uuid) {
|
||||
if (uuid == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.util.*;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
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.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@Slf4j
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Autowired private CustomUserDetailsService userDetailsService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
@Autowired(required = false)
|
||||
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
@@ -73,12 +74,15 @@ public class SecurityConfiguration {
|
||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||
|
||||
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
http.authenticationManager(authenticationManager(http));
|
||||
|
||||
if (loginEnabledValue) {
|
||||
|
||||
http.addFilterBefore(
|
||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
@@ -135,6 +139,7 @@ public class SecurityConfiguration {
|
||||
|
||||
return trimmedUri.startsWith("/login")
|
||||
|| trimmedUri.startsWith("/oauth")
|
||||
|| trimmedUri.startsWith("/saml2")
|
||||
|| trimmedUri.endsWith(".svg")
|
||||
|| trimmedUri.startsWith(
|
||||
"/register")
|
||||
@@ -152,8 +157,8 @@ public class SecurityConfiguration {
|
||||
.authenticated());
|
||||
|
||||
// Handle OAUTH2 Logins
|
||||
if (applicationProperties.getSecurity().getOAUTH2() != null
|
||||
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()
|
||||
if (applicationProperties.getSecurity().getOauth2() != null
|
||||
&& applicationProperties.getSecurity().getOauth2().getEnabled()
|
||||
&& !applicationProperties
|
||||
.getSecurity()
|
||||
.getLoginMethod()
|
||||
@@ -184,13 +189,37 @@ public class SecurityConfiguration {
|
||||
userService,
|
||||
loginAttemptService))
|
||||
.userAuthoritiesMapper(
|
||||
userAuthoritiesMapper())))
|
||||
userAuthoritiesMapper)))
|
||||
.logout(
|
||||
logout ->
|
||||
logout.logoutSuccessHandler(
|
||||
new CustomOAuth2LogoutSuccessHandler(
|
||||
applicationProperties)));
|
||||
}
|
||||
|
||||
// Handle SAML
|
||||
if (applicationProperties.getSecurity().getSaml() != null
|
||||
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
||||
&& !applicationProperties
|
||||
.getSecurity()
|
||||
.getLoginMethod()
|
||||
.equalsIgnoreCase("normal")) {
|
||||
http.saml2Login(
|
||||
saml2 -> {
|
||||
saml2.relyingPartyRegistrationRepository(
|
||||
relyingPartyRegistrationRepository)
|
||||
.successHandler(
|
||||
new CustomSAMLAuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
userService,
|
||||
applicationProperties))
|
||||
.failureHandler(
|
||||
new CustomSAMLAuthenticationFailureHandler());
|
||||
})
|
||||
.saml2Logout(withDefaults())
|
||||
.addFilterBefore(
|
||||
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
||||
}
|
||||
} else {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||
@@ -199,176 +228,32 @@ public class SecurityConfiguration {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// Client Registration Repository for OAUTH2 OIDC Login
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
|
||||
githubClientRegistration().ifPresent(registrations::add);
|
||||
oidcClientRegistration().ifPresent(registrations::add);
|
||||
googleClientRegistration().ifPresent(registrations::add);
|
||||
keycloakClientRegistration().ifPresent(registrations::add);
|
||||
|
||||
if (registrations.isEmpty()) {
|
||||
logger.error("At least one OAuth2 provider must be configured");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
public AuthenticationProvider samlAuthenticationProvider() {
|
||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||
new OpenSaml4AuthenticationProvider();
|
||||
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||
return authenticationProvider;
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> googleClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GoogleProvider google = client.getGoogle();
|
||||
return google != null && google.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId(google.getName())
|
||||
.clientId(google.getClientId())
|
||||
.clientSecret(google.getClientSecret())
|
||||
.scope(google.getScopes())
|
||||
.authorizationUri(google.getAuthorizationuri())
|
||||
.tokenUri(google.getTokenuri())
|
||||
.userInfoUri(google.getUserinfouri())
|
||||
.userNameAttributeName(google.getUseAsUsername())
|
||||
.clientName(google.getClientName())
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
|
||||
return keycloak != null && keycloak.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||
.registrationId(keycloak.getName())
|
||||
.clientId(keycloak.getClientId())
|
||||
.clientSecret(keycloak.getClientSecret())
|
||||
.scope(keycloak.getScopes())
|
||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||
.clientName(keycloak.getClientName())
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> githubClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GithubProvider github = client.getGithub();
|
||||
return github != null && github.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId(github.getName())
|
||||
.clientId(github.getClientId())
|
||||
.clientSecret(github.getClientSecret())
|
||||
.scope(github.getScopes())
|
||||
.authorizationUri(github.getAuthorizationuri())
|
||||
.tokenUri(github.getTokenuri())
|
||||
.userInfoUri(github.getUserinfouri())
|
||||
.userNameAttributeName(github.getUseAsUsername())
|
||||
.clientName(github.getClientName())
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null
|
||||
|| oauth.getIssuer() == null
|
||||
|| oauth.getIssuer().isEmpty()
|
||||
|| oauth.getClientId() == null
|
||||
|| oauth.getClientId().isEmpty()
|
||||
|| oauth.getClientSecret() == null
|
||||
|| oauth.getClientSecret().isEmpty()
|
||||
|| oauth.getScopes() == null
|
||||
|| oauth.getScopes().isEmpty()
|
||||
|| oauth.getUseAsUsername() == null
|
||||
|| oauth.getUseAsUsername().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(oauth.getClientId())
|
||||
.clientSecret(oauth.getClientSecret())
|
||||
.scope(oauth.getScopes())
|
||||
.userNameAttributeName(oauth.getUseAsUsername())
|
||||
.clientName("OIDC")
|
||||
.build());
|
||||
}
|
||||
|
||||
/*
|
||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
return (authorities) -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
public AuthenticationProvider daoAuthenticationProvider() {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||
provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||
return provider;
|
||||
}
|
||||
|
||||
authorities.forEach(
|
||||
authority -> {
|
||||
// Add existing OAUTH2 Authorities
|
||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||
|
||||
// Add Authorities from database for existing user, if user is present.
|
||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||
String useAsUsername =
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getOAUTH2()
|
||||
.getUseAsUsername();
|
||||
Optional<User> userOpt =
|
||||
userService.findByUsernameIgnoreCase(
|
||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
if (user != null) {
|
||||
mappedAuthorities.add(
|
||||
new SimpleGrantedAuthority(
|
||||
userService.findRole(user).getAuthority()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return mappedAuthorities;
|
||||
};
|
||||
authenticationManagerBuilder
|
||||
.authenticationProvider(daoAuthenticationProvider()) // Benutzername/Passwort
|
||||
.authenticationProvider(samlAuthenticationProvider()); // SAML
|
||||
|
||||
return authenticationManagerBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -386,4 +271,13 @@ public class SecurityConfiguration {
|
||||
public boolean activSecurity() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only Dev test
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) ->
|
||||
web.ignoring()
|
||||
.requestMatchers(
|
||||
"/css/**", "/images/**", "/js/**", "/**.svg", "/pdfjs-legacy/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -30,13 +29,18 @@ import stirling.software.SPDF.model.User;
|
||||
@Component
|
||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired @Lazy private UserService userService;
|
||||
private final UserService userService;
|
||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private final boolean loginEnabledValue;
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("loginEnabled")
|
||||
public boolean loginEnabledValue;
|
||||
public UserAuthenticationFilter(
|
||||
@Lazy UserService userService,
|
||||
SessionPersistentRegistry sessionPersistentRegistry,
|
||||
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||
this.userService = userService;
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
this.loginEnabledValue = loginEnabledValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@@ -51,6 +55,19 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
String requestURI = request.getRequestURI();
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
// Check for session expiration (unsure if needed)
|
||||
// if (authentication != null && authentication.isAuthenticated()) {
|
||||
// String sessionId = request.getSession().getId();
|
||||
// SessionInformation sessionInfo =
|
||||
// sessionPersistentRegistry.getSessionInformation(sessionId);
|
||||
//
|
||||
// if (sessionInfo != null && sessionInfo.isExpired()) {
|
||||
// SecurityContextHolder.clearContext();
|
||||
// response.sendRedirect(request.getContextPath() + "/login?expired=true");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
@@ -159,7 +176,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
};
|
||||
|
||||
for (String pattern : permitAllPatterns) {
|
||||
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
||||
if (uri.startsWith(pattern)
|
||||
|| uri.endsWith(".svg")
|
||||
|| uri.endsWith(".png")
|
||||
|| uri.endsWith(".ico")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@@ -18,7 +19,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
@@ -342,4 +343,14 @@ public class UserService implements UserServiceInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getCurrentUsername() {
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
|
||||
if (principal instanceof UserDetails) {
|
||||
return ((UserDetails) principal).getUsername();
|
||||
} else {
|
||||
return principal.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.utils.FileInfo;
|
||||
|
||||
@Slf4j
|
||||
|
||||
@@ -66,7 +66,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
||||
// Redirect to the original destination
|
||||
super.onAuthenticationSuccess(request, response, authentication);
|
||||
} else {
|
||||
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
|
||||
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||
|
||||
if (loginAttemptService.isBlocked(username)) {
|
||||
if (session != null) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
}
|
||||
return;
|
||||
}
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
|
||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
||||
|
||||
@Override
|
||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2();
|
||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
||||
String usernameAttribute = oauth2.getUseAsUsername();
|
||||
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
|
||||
Client client = oauth2.getClient();
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ConvertResponseToAuthentication
|
||||
implements Converter<ResponseToken, Saml2Authentication> {
|
||||
|
||||
private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup;
|
||||
|
||||
public ConvertResponseToAuthentication(
|
||||
Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) {
|
||||
this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||
final Assertion assertion =
|
||||
CollectionUtils.firstElement(responseToken.getResponse().getAssertions());
|
||||
final Map<String, List<Object>> attributes =
|
||||
SamlAssertionUtils.getAssertionAttributes(assertion);
|
||||
final String registrationId =
|
||||
responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
|
||||
final ScimSaml2AuthenticatedPrincipal principal =
|
||||
new ScimSaml2AuthenticatedPrincipal(
|
||||
assertion,
|
||||
attributes,
|
||||
saml2AuthorityAttributeLookup.getIdentityMappings(registrationId));
|
||||
final Collection<? extends GrantedAuthority> assertionAuthorities =
|
||||
getAssertionAuthorities(
|
||||
attributes,
|
||||
saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId));
|
||||
return new Saml2Authentication(
|
||||
principal, responseToken.getToken().getSaml2Response(), assertionAuthorities);
|
||||
}
|
||||
|
||||
private static Collection<? extends GrantedAuthority> getAssertionAuthorities(
|
||||
final Map<String, List<Object>> attributes, final String authoritiesAttributeName) {
|
||||
if (attributes == null || attributes.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
List<Object> groups = new ArrayList<>();
|
||||
|
||||
if (attributes.get(authoritiesAttributeName) != null) {
|
||||
groups = new ArrayList<>(attributes.get(authoritiesAttributeName));
|
||||
}
|
||||
|
||||
return groups.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.map(String::toLowerCase)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception)
|
||||
throws IOException, ServletException {
|
||||
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
log.error("BadCredentialsException", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof DisabledException) {
|
||||
log.error("User is deactivated: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof LockedException) {
|
||||
log.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof Saml2AuthenticationException) {
|
||||
log.error("SAML2 Authentication error: ", exception);
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||
return;
|
||||
}
|
||||
log.error("Unhandled authentication exception", exception);
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Slf4j
|
||||
public class CustomSAMLAuthenticationSuccessHandler
|
||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||
|
||||
private LoginAttemptService loginAttemptService;
|
||||
private UserService userService;
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
public CustomSAMLAuthenticationSuccessHandler(
|
||||
LoginAttemptService loginAttemptService,
|
||||
UserService userService,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.loginAttemptService = loginAttemptService;
|
||||
this.userService = userService;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws ServletException, IOException {
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username = "";
|
||||
|
||||
if (principal instanceof OAuth2User) {
|
||||
OAuth2User oauthUser = (OAuth2User) principal;
|
||||
username = oauthUser.getName();
|
||||
} else if (principal instanceof UserDetails) {
|
||||
UserDetails oauthUser = (UserDetails) principal;
|
||||
username = oauthUser.getUsername();
|
||||
} else if (principal instanceof ScimSaml2AuthenticatedPrincipal) {
|
||||
ScimSaml2AuthenticatedPrincipal samlPrincipal =
|
||||
(ScimSaml2AuthenticatedPrincipal) principal;
|
||||
username = samlPrincipal.getName();
|
||||
}
|
||||
|
||||
// Get the saved request
|
||||
HttpSession session = request.getSession(false);
|
||||
String contextPath = request.getContextPath();
|
||||
SavedRequest savedRequest =
|
||||
(session != null)
|
||||
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||
: null;
|
||||
|
||||
if (savedRequest != null
|
||||
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
|
||||
// Redirect to the original destination
|
||||
super.onAuthenticationSuccess(request, response, authentication);
|
||||
} else {
|
||||
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||
|
||||
if (loginAttemptService.isBlocked(username)) {
|
||||
if (session != null) {
|
||||
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||
}
|
||||
throw new LockedException(
|
||||
"Your account has been locked due to too many failed login attempts.");
|
||||
}
|
||||
if (userService.usernameExistsIgnoreCase(username)
|
||||
&& userService.hasPassword(username)
|
||||
&& !userService.isAuthenticationTypeByUsername(
|
||||
username, AuthenticationType.OAUTH2)
|
||||
&& oAuth.getAutoCreateUser()) {
|
||||
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (oAuth.getBlockRegistration()
|
||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||
return;
|
||||
}
|
||||
if (principal instanceof OAuth2User) {
|
||||
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||
}
|
||||
response.sendRedirect(contextPath + "/");
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String redirectUrl = determineTargetUrl(request, response, authentication);
|
||||
|
||||
if (response.isCommitted()) {
|
||||
log.debug("Response has already been committed. Unable to redirect to " + redirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
}
|
||||
|
||||
protected String determineTargetUrl(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication) {
|
||||
// Default to the root URL
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
public interface Saml2AuthorityAttributeLookup {
|
||||
String getAuthorityAttribute(String registrationId);
|
||||
|
||||
SimpleScimMappings getIdentityMappings(String registrationId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup {
|
||||
|
||||
@Override
|
||||
public String getAuthorityAttribute(String registrationId) {
|
||||
return "authorityAttributeName";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleScimMappings getIdentityMappings(String registrationId) {
|
||||
return new SimpleScimMappings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.schema.*;
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
|
||||
public class SamlAssertionUtils {
|
||||
|
||||
public static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
|
||||
Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
|
||||
|
||||
assertion
|
||||
.getAttributeStatements()
|
||||
.forEach(
|
||||
attributeStatement -> {
|
||||
attributeStatement
|
||||
.getAttributes()
|
||||
.forEach(
|
||||
attribute -> {
|
||||
List<Object> attributeValues = new ArrayList<>();
|
||||
|
||||
attribute
|
||||
.getAttributeValues()
|
||||
.forEach(
|
||||
xmlObject -> {
|
||||
Object attributeValue =
|
||||
getXmlObjectValue(
|
||||
xmlObject);
|
||||
if (attributeValue != null) {
|
||||
attributeValues.add(
|
||||
attributeValue);
|
||||
}
|
||||
});
|
||||
|
||||
attributeMap.put(
|
||||
attribute.getName(), attributeValues);
|
||||
});
|
||||
});
|
||||
|
||||
return attributeMap;
|
||||
}
|
||||
|
||||
public static Object getXmlObjectValue(XMLObject xmlObject) {
|
||||
if (xmlObject instanceof XSAny) {
|
||||
return ((XSAny) xmlObject).getTextContent();
|
||||
} else if (xmlObject instanceof XSString) {
|
||||
return ((XSString) xmlObject).getValue();
|
||||
} else if (xmlObject instanceof XSInteger) {
|
||||
return ((XSInteger) xmlObject).getValue();
|
||||
} else if (xmlObject instanceof XSURI) {
|
||||
return ((XSURI) xmlObject).getURI();
|
||||
} else if (xmlObject instanceof XSBoolean) {
|
||||
return ((XSBoolean) xmlObject).getValue().getValue();
|
||||
} else if (xmlObject instanceof XSDateTime) {
|
||||
Instant dateTime = ((XSDateTime) xmlObject).getValue();
|
||||
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
|
||||
import org.opensaml.security.x509.X509Support;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.security.converter.RsaKeyConverters;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.registration.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class SamlConfig {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired ResourceLoader resourceLoader;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.saml.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository()
|
||||
throws CertificateException, IOException {
|
||||
|
||||
// Resource signingCertResource = new ClassPathResource(this.rpSigningCertLocation);
|
||||
Resource signingCertResource =
|
||||
resourceLoader.getResource(
|
||||
this.applicationProperties
|
||||
.getSecurity()
|
||||
.getSaml()
|
||||
.getCertificateLocation());
|
||||
// Resource signingKeyResource = new ClassPathResource(this.rpSigningKeyLocation);
|
||||
Resource signingKeyResource =
|
||||
resourceLoader.getResource(
|
||||
this.applicationProperties.getSecurity().getSaml().getPrivateKeyLocation());
|
||||
try (InputStream is = signingKeyResource.getInputStream();
|
||||
InputStream certIS = signingCertResource.getInputStream(); ) {
|
||||
X509Certificate rpCertificate = X509Support.decodeCertificate(certIS.readAllBytes());
|
||||
RSAPrivateKey rpKey = RsaKeyConverters.pkcs8().convert(is);
|
||||
final Saml2X509Credential rpSigningCredentials =
|
||||
Saml2X509Credential.signing(rpKey, rpCertificate);
|
||||
|
||||
X509Certificate apCert =
|
||||
X509Support.decodeCertificate(
|
||||
applicationProperties.getSecurity().getSaml().getSigningCertificate());
|
||||
Saml2X509Credential apCredential = Saml2X509Credential.verification(apCert);
|
||||
|
||||
RelyingPartyRegistration registration =
|
||||
RelyingPartyRegistrations.fromMetadataLocation(
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getSaml()
|
||||
.getIdpMetadataLocation())
|
||||
.entityId(applicationProperties.getSecurity().getSaml().getEntityId())
|
||||
.registrationId(
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getSaml()
|
||||
.getRegistrationId())
|
||||
.signingX509Credentials(c -> c.add(rpSigningCredentials))
|
||||
.assertingPartyDetails(
|
||||
party ->
|
||||
party.wantAuthnRequestsSigned(true)
|
||||
.verificationX509Credentials(
|
||||
c -> c.add(apCredential)))
|
||||
.singleLogoutServiceLocation("http://localhost:8090/logout/saml2/slo")
|
||||
.build();
|
||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.unboundid.scim2.common.types.Email;
|
||||
import com.unboundid.scim2.common.types.Name;
|
||||
import com.unboundid.scim2.common.types.UserResource;
|
||||
|
||||
public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final transient UserResource userResource;
|
||||
|
||||
public ScimSaml2AuthenticatedPrincipal(
|
||||
final Assertion assertion,
|
||||
final Map<String, List<Object>> attributes,
|
||||
final SimpleScimMappings attributeMappings) {
|
||||
Assert.notNull(assertion, "assertion cannot be null");
|
||||
Assert.notNull(assertion.getSubject(), "assertion subject cannot be null");
|
||||
Assert.notNull(
|
||||
assertion.getSubject().getNameID(), "assertion subject NameID cannot be null");
|
||||
Assert.notNull(attributes, "attributes cannot be null");
|
||||
Assert.notNull(attributeMappings, "attributeMappings cannot be null");
|
||||
|
||||
final Name name =
|
||||
new Name()
|
||||
.setFamilyName(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getFamilyName))
|
||||
.setGivenName(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getGivenName));
|
||||
|
||||
final List<Email> emails = new ArrayList<>(1);
|
||||
emails.add(
|
||||
new Email()
|
||||
.setValue(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getEmail))
|
||||
.setPrimary(true));
|
||||
|
||||
userResource =
|
||||
new UserResource()
|
||||
.setUserName(assertion.getSubject().getNameID().getValue())
|
||||
.setName(name)
|
||||
.setEmails(emails);
|
||||
}
|
||||
|
||||
private static String getAttribute(
|
||||
final Map<String, List<Object>> attributes,
|
||||
final SimpleScimMappings simpleScimMappings,
|
||||
final Function<SimpleScimMappings, String> attributeMapper) {
|
||||
|
||||
final String key = attributeMapper.apply(simpleScimMappings);
|
||||
|
||||
final List<Object> values = attributes.getOrDefault(key, Collections.emptyList());
|
||||
|
||||
return values.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.userResource.getUserName();
|
||||
}
|
||||
|
||||
public UserResource getUserResource() {
|
||||
return this.userResource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SimpleScimMappings {
|
||||
String givenName;
|
||||
String familyName;
|
||||
String email;
|
||||
}
|
||||
@@ -11,16 +11,19 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||
super();
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
log.info("Session created: " + se.getSession().getId());
|
||||
}
|
||||
public void sessionCreated(HttpSessionEvent se) {}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
log.info("Session destroyed: " + se.getSession().getId());
|
||||
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
}
|
||||
|
||||
if (principalName != null) {
|
||||
// Clear old sessions for the principal (unsure if needed)
|
||||
// List<SessionEntity> existingSessions =
|
||||
// sessionRepository.findByPrincipalName(principalName);
|
||||
// for (SessionEntity session : existingSessions) {
|
||||
// session.setExpired(true);
|
||||
// sessionRepository.save(session);
|
||||
// }
|
||||
|
||||
SessionEntity sessionEntity = new SessionEntity();
|
||||
sessionEntity.setSessionId(sessionId);
|
||||
sessionEntity.setPrincipalName(principalName);
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -23,6 +24,8 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.service.PostHogService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -32,6 +35,17 @@ public class CropController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
private final PostHogService postHogService;
|
||||
|
||||
@Autowired
|
||||
public CropController(
|
||||
CustomPDDocumentFactory pdfDocumentFactory, PostHogService postHogService) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.postHogService = postHogService;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Crops a PDF document",
|
||||
@@ -40,7 +54,8 @@ public class CropController {
|
||||
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
|
||||
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
|
||||
|
||||
PDDocument newDocument = new PDDocument();
|
||||
PDDocument newDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
|
||||
int totalPages = sourceDocument.getNumberOfPages();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -43,9 +45,16 @@ public class MergeController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public MergeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
// Merges a list of PDDocument objects into a single PDDocument
|
||||
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||
PDDocument mergedDoc = new PDDocument();
|
||||
PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();
|
||||
for (PDDocument doc : documents) {
|
||||
for (PDPage page : doc.getPages()) {
|
||||
mergedDoc.addPage(page);
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -35,6 +37,13 @@ public class MultiPageLayoutController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public MultiPageLayoutController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Merge multiple pages of a PDF document into a single page",
|
||||
@@ -60,7 +69,8 @@ public class MultiPageLayoutController {
|
||||
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
||||
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
PDDocument newDocument = new PDDocument();
|
||||
PDDocument newDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
PDPage newPage = new PDPage(PDRectangle.A4);
|
||||
newDocument.addPage(newPage);
|
||||
|
||||
|
||||
@@ -3,16 +3,15 @@ package stirling.software.SPDF.controller.api;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.service.PdfImageRemovalService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -25,15 +24,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
public class PdfImageRemovalController {
|
||||
|
||||
// Service for removing images from PDFs
|
||||
@Autowired private PdfImageRemovalService pdfImageRemovalService;
|
||||
private final PdfImageRemovalService pdfImageRemovalService;
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
/**
|
||||
* Constructor for dependency injection of PdfImageRemovalService.
|
||||
*
|
||||
* @param pdfImageRemovalService The service used for removing images from PDFs.
|
||||
*/
|
||||
public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) {
|
||||
@Autowired
|
||||
public PdfImageRemovalController(
|
||||
PdfImageRemovalService pdfImageRemovalService,
|
||||
CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfImageRemovalService = pdfImageRemovalService;
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,14 +58,8 @@ public class PdfImageRemovalController {
|
||||
description =
|
||||
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
|
||||
|
||||
MultipartFile pdf = file.getFileInput();
|
||||
|
||||
// Convert the MultipartFile to a byte array
|
||||
byte[] pdfBytes = pdf.getBytes();
|
||||
|
||||
// Load the PDF document from the byte array
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
// Load the PDF document
|
||||
PDDocument document = pdfDocumentFactory.load(file);
|
||||
|
||||
// Remove images from the PDF document using the service
|
||||
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
|
||||
@@ -74,7 +73,8 @@ public class PdfImageRemovalController {
|
||||
|
||||
// Generate a new filename for the modified PDF
|
||||
String mergedFileName =
|
||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf";
|
||||
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||
+ "_removed_images.pdf";
|
||||
|
||||
// Convert the byte array to a web response and return it
|
||||
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.Map;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.multipdf.Overlay;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -33,6 +35,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "General", description = "General APIs")
|
||||
public class PdfOverlayController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Overlay PDF files in various modes",
|
||||
@@ -56,7 +65,7 @@ public class PdfOverlayController {
|
||||
// "FixedRepeatOverlay"
|
||||
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
|
||||
|
||||
try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes());
|
||||
try (PDDocument basePdf = pdfDocumentFactory.load(baseFile);
|
||||
Overlay overlay = new Overlay()) {
|
||||
Map<Integer, String> overlayGuide =
|
||||
prepareOverlayGuide(
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.SortTypes;
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -34,6 +36,13 @@ public class RearrangePagesPDFController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||
@Operation(
|
||||
summary = "Remove pages from a PDF file",
|
||||
@@ -45,7 +54,7 @@ public class RearrangePagesPDFController {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pagesToDelete = request.getPageNumbers();
|
||||
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||
|
||||
// Split the page order string into an array of page numbers or range of numbers
|
||||
String[] pageOrderArr = pagesToDelete.split(",");
|
||||
|
||||
@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -29,6 +30,13 @@ public class RotationController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public RotationController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||
@Operation(
|
||||
summary = "Rotate a PDF file",
|
||||
@@ -39,7 +47,7 @@ public class RotationController {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
Integer angle = request.getAngle();
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(request);
|
||||
|
||||
// Get the list of pages in the document
|
||||
PDPageTree pages = document.getPages();
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -27,6 +28,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -36,6 +38,13 @@ public class ScalePagesController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ScalePagesController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Change the size of a PDF page/document",
|
||||
@@ -47,29 +56,11 @@ public class ScalePagesController {
|
||||
String targetPDRectangle = request.getPageSize();
|
||||
float scaleFactor = request.getScaleFactor();
|
||||
|
||||
Map<String, PDRectangle> sizeMap = new HashMap<>();
|
||||
// Add A0 - A10
|
||||
sizeMap.put("A0", PDRectangle.A0);
|
||||
sizeMap.put("A1", PDRectangle.A1);
|
||||
sizeMap.put("A2", PDRectangle.A2);
|
||||
sizeMap.put("A3", PDRectangle.A3);
|
||||
sizeMap.put("A4", PDRectangle.A4);
|
||||
sizeMap.put("A5", PDRectangle.A5);
|
||||
sizeMap.put("A6", PDRectangle.A6);
|
||||
|
||||
// Add other sizes
|
||||
sizeMap.put("LETTER", PDRectangle.LETTER);
|
||||
sizeMap.put("LEGAL", PDRectangle.LEGAL);
|
||||
|
||||
if (!sizeMap.containsKey(targetPDRectangle)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
|
||||
}
|
||||
|
||||
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
|
||||
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
PDDocument outputDocument = new PDDocument();
|
||||
PDDocument outputDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
|
||||
PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);
|
||||
|
||||
int totalPages = sourceDocument.getNumberOfPages();
|
||||
for (int i = 0; i < totalPages; i++) {
|
||||
@@ -116,4 +107,45 @@ public class ScalePagesController {
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||
+ "_scaled.pdf");
|
||||
}
|
||||
|
||||
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||
if (targetPDRectangle.equals("KEEP")) {
|
||||
if (sourceDocument.getNumberOfPages() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// use the first page to determine the target page size
|
||||
PDPage sourcePage = sourceDocument.getPage(0);
|
||||
PDRectangle sourceSize = sourcePage.getMediaBox();
|
||||
|
||||
return sourceSize;
|
||||
}
|
||||
|
||||
Map<String, PDRectangle> sizeMap = getSizeMap();
|
||||
|
||||
if (sizeMap.containsKey(targetPDRectangle)) {
|
||||
return sizeMap.get(targetPDRectangle);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL, KEEP");
|
||||
}
|
||||
|
||||
private Map<String, PDRectangle> getSizeMap() {
|
||||
Map<String, PDRectangle> sizeMap = new HashMap<>();
|
||||
// Add A0 - A6
|
||||
sizeMap.put("A0", PDRectangle.A0);
|
||||
sizeMap.put("A1", PDRectangle.A1);
|
||||
sizeMap.put("A2", PDRectangle.A2);
|
||||
sizeMap.put("A3", PDRectangle.A3);
|
||||
sizeMap.put("A4", PDRectangle.A4);
|
||||
sizeMap.put("A5", PDRectangle.A5);
|
||||
sizeMap.put("A6", PDRectangle.A6);
|
||||
|
||||
// Add other sizes
|
||||
sizeMap.put("LETTER", PDRectangle.LETTER);
|
||||
sizeMap.put("LEGAL", PDRectangle.LEGAL);
|
||||
|
||||
return sizeMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Controller
|
||||
@Tag(name = "Settings", description = "Settings APIs")
|
||||
@RequestMapping("/api/v1/settings")
|
||||
@Hidden
|
||||
public class SettingsController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@PostMapping("/update-enable-analytics")
|
||||
@Hidden
|
||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||
.body(
|
||||
"Setting has already been set, To adjust please edit /config/settings.yml");
|
||||
}
|
||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||
return ResponseEntity.ok("Updated");
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -27,9 +28,8 @@ import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -38,6 +38,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
public class SplitPDFController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public SplitPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||
@Operation(
|
||||
@@ -51,11 +57,9 @@ public class SplitPDFController {
|
||||
// open the pdf document
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
|
||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||
System.out.println(
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
if (!pageNumbers.contains(totalPages - 1)) {
|
||||
// Create a mutable ArrayList so we can add to it
|
||||
pageNumbers = new ArrayList<>(pageNumbers);
|
||||
@@ -70,7 +74,8 @@ public class SplitPDFController {
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||
int previousPageNumber = 0;
|
||||
for (int splitPoint : pageNumbers) {
|
||||
try (PDDocument splitDocument = new PDDocument()) {
|
||||
try (PDDocument splitDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
splitDocument.addPage(page);
|
||||
@@ -79,7 +84,7 @@ public class SplitPDFController {
|
||||
previousPageNumber = splitPoint + 1;
|
||||
|
||||
// Transfer metadata to split pdf
|
||||
PdfUtils.setMetadataToPdf(splitDocument, metadata);
|
||||
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocume
|
||||
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.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -33,7 +34,7 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.service.PdfMetadataService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -44,6 +45,13 @@ public class SplitPdfByChaptersController {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
|
||||
|
||||
private final PdfMetadataService pdfMetadataService;
|
||||
|
||||
@Autowired
|
||||
public SplitPdfByChaptersController(PdfMetadataService pdfMetadataService) {
|
||||
this.pdfMetadataService = pdfMetadataService;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Split PDFs by Chapters",
|
||||
@@ -59,15 +67,6 @@ public class SplitPdfByChaptersController {
|
||||
}
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// checks if the document is encrypted by an empty user password
|
||||
if (sourceDocument.isEncrypted()) {
|
||||
try {
|
||||
sourceDocument.setAllSecurityToBeRemoved(true);
|
||||
logger.info("Removing security from the source document ");
|
||||
} catch (Exception e) {
|
||||
logger.warn("Cannot decrypt the pdf");
|
||||
}
|
||||
}
|
||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
@@ -258,7 +257,7 @@ public class SplitPdfByChaptersController {
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||
PdfMetadata metadata = null;
|
||||
if (includeMetadata) {
|
||||
metadata = PdfUtils.extractMetadataFromPdf(sourceDocument);
|
||||
metadata = pdfMetadataService.extractMetadataFromPdf(sourceDocument);
|
||||
}
|
||||
for (Bookmark bookmark : bookmarks) {
|
||||
try (PDDocument splitDocument = new PDDocument()) {
|
||||
@@ -273,7 +272,7 @@ public class SplitPdfByChaptersController {
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (includeMetadata) {
|
||||
PdfUtils.setMetadataToPdf(splitDocument, metadata);
|
||||
pdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||
}
|
||||
|
||||
splitDocument.save(baos);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -43,6 +45,13 @@ public class SplitPdfBySectionsController {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public SplitPdfBySectionsController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Split PDF pages into smaller sections",
|
||||
@@ -65,7 +74,7 @@ public class SplitPdfBySectionsController {
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
if (merge) {
|
||||
MergeController mergeController = new MergeController();
|
||||
MergeController mergeController = new MergeController(pdfDocumentFactory);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
mergeController.mergeDocuments(splitDocuments).save(baos);
|
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -34,6 +36,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
public class SplitPdfBySizeController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public SplitPdfBySizeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
@@ -84,7 +92,8 @@ public class SplitPdfBySizeController {
|
||||
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
long currentSize = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
PDDocument currentDoc =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
int fileIndex = 1;
|
||||
|
||||
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
|
||||
@@ -121,7 +130,8 @@ public class SplitPdfBySizeController {
|
||||
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
int currentPageCount = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
PDDocument currentDoc =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
int fileIndex = 1;
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
currentDoc.addPage(page);
|
||||
@@ -152,7 +162,8 @@ public class SplitPdfBySizeController {
|
||||
int currentPageIndex = 0;
|
||||
int fileIndex = 1;
|
||||
for (int i = 0; i < documentCount; i++) {
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
PDDocument currentDoc =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||
|
||||
for (int j = 0; j < pagesToAdd; j++) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -32,6 +34,13 @@ public class ToSinglePageController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ToSinglePageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
||||
@Operation(
|
||||
summary = "Convert a multi-page PDF into a single long page PDF",
|
||||
@@ -53,7 +62,8 @@ public class ToSinglePageController {
|
||||
}
|
||||
|
||||
// Create new document and page with calculated dimensions
|
||||
PDDocument newDocument = new PDDocument();
|
||||
PDDocument newDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
|
||||
newDocument.addPage(newPage);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
@@ -40,6 +41,7 @@ import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||
@Controller
|
||||
@Tag(name = "User", description = "User APIs")
|
||||
@RequestMapping("/api/v1/user")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
@@ -191,13 +193,11 @@ public class UserController {
|
||||
Map<String, String[]> paramMap = request.getParameterMap();
|
||||
Map<String, String> updates = new HashMap<>();
|
||||
|
||||
System.out.println("Received parameter map: " + paramMap);
|
||||
|
||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||
}
|
||||
|
||||
System.out.println("Processed updates: " + updates);
|
||||
log.debug("Processed updates: " + updates);
|
||||
|
||||
// Assuming you have a method in userService to update the settings for a user
|
||||
userService.updateUserSettings(principal.getName(), updates);
|
||||
@@ -208,7 +208,7 @@ public class UserController {
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/saveUser")
|
||||
public RedirectView saveUser(
|
||||
@RequestParam String username,
|
||||
@RequestParam(name = "username", required = true) String username,
|
||||
@RequestParam(name = "password", required = false) String password,
|
||||
@RequestParam(name = "role") String role,
|
||||
@RequestParam(name = "authType") String authType,
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.GeneralFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
public class ConvertBookToPDFController {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
private final boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
// @Autowired
|
||||
public ConvertBookToPDFController(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
||||
@Operation(
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
public class ConvertHtmlToPDF {
|
||||
|
||||
@Autowired
|
||||
// @Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
@@ -43,6 +45,13 @@ public class ConvertImgPDFController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertImgPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
||||
@Operation(
|
||||
summary = "Convert PDF to image(s)",
|
||||
@@ -178,7 +187,8 @@ public class ConvertImgPDFController {
|
||||
boolean autoRotate = request.isAutoRotate();
|
||||
|
||||
// Convert the file to PDF and get the resulting bytes
|
||||
byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType);
|
||||
byte[] bytes =
|
||||
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
bytes,
|
||||
file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||
|
||||
@@ -10,29 +10,26 @@ import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.AttributeProvider;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.GeneralFile;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
public class ConvertMarkdownToPdf {
|
||||
|
||||
@Autowired
|
||||
// @Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -8,6 +9,8 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -20,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.GeneralFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@@ -29,7 +33,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertOfficeController {
|
||||
|
||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
// Check for valid file extension
|
||||
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
if (originalFilename == null
|
||||
@@ -62,12 +66,10 @@ public class ConvertOfficeController {
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the converted PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
return pdfBytes;
|
||||
return tempOutputFile.toFile();
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +78,13 @@ public class ConvertOfficeController {
|
||||
return fileExtension.matches(extensionPattern);
|
||||
}
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
||||
@Operation(
|
||||
summary = "Convert a file to a PDF using LibreOffice",
|
||||
@@ -86,12 +95,18 @@ public class ConvertOfficeController {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
// unused but can start server instance if startup time is to long
|
||||
// LibreOfficeListener.getInstance().start();
|
||||
File file = null;
|
||||
try {
|
||||
file = convertToPdf(inputFile);
|
||||
|
||||
byte[] pdfByteArray = convertToPdf(inputFile);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfByteArray,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_convertedToPDF.pdf");
|
||||
PDDocument doc = pdfDocumentFactory.load(file);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
doc,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_convertedToPDF.pdf");
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,30 +6,27 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
public class ConvertPDFToBookController {
|
||||
|
||||
@Autowired
|
||||
// @Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -53,32 +46,7 @@ public class ConvertPDFToPDFA {
|
||||
// Convert MultipartFile to byte[]
|
||||
byte[] pdfBytes = inputFile.getBytes();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
|
||||
// Get the document catalog
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
|
||||
// Get the AcroForm
|
||||
PDAcroForm acroForm = catalog.getAcroForm();
|
||||
if (acroForm != null) {
|
||||
// Remove signature fields safely
|
||||
List<PDField> fieldsToRemove =
|
||||
acroForm.getFields().stream()
|
||||
.filter(field -> field instanceof PDSignatureField)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
acroForm.flatten(fieldsToRemove, false);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
}
|
||||
document.close();
|
||||
|
||||
// Save the uploaded (and possibly modified) file to a temporary location
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
||||
outputStream.write(pdfBytes);
|
||||
@@ -87,32 +55,41 @@ public class ConvertPDFToPDFA {
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Prepare the OCRmyPDF command
|
||||
// Prepare the ghostscript command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("ocrmypdf");
|
||||
command.add("--skip-text");
|
||||
command.add("--tesseract-timeout=0");
|
||||
command.add("--output-type");
|
||||
command.add(outputFormat.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
command.add("gs");
|
||||
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
|
||||
command.add("-dNOPAUSE");
|
||||
command.add("-dBATCH");
|
||||
command.add("-sColorConversionStrategy=sRGB");
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add("-dPDFACompatibilityPolicy=2");
|
||||
command.add("-o");
|
||||
command.add(tempOutputFile.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
if (returnCode.getRc() != 0) {
|
||||
logger.info(
|
||||
outputFormat + " conversion failed with return code: " + returnCode.getRc());
|
||||
}
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_PDFA.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename);
|
||||
try {
|
||||
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile);
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_PDFA.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -16,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
@@ -26,6 +31,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertWebsiteToPDF {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertWebsiteToPDF(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||
@Operation(
|
||||
summary = "Convert a URL to a PDF",
|
||||
@@ -46,12 +60,12 @@ public class ConvertWebsiteToPDF {
|
||||
}
|
||||
|
||||
Path tempOutputFile = null;
|
||||
byte[] pdfBytes;
|
||||
PDDocument doc = null;
|
||||
try {
|
||||
// Prepare the output file path
|
||||
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Prepare the OCRmyPDF command
|
||||
// Prepare the WeasyPrint command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("weasyprint");
|
||||
command.add(URL);
|
||||
@@ -61,16 +75,23 @@ public class ConvertWebsiteToPDF {
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
// Load the PDF using pdfDocumentFactory
|
||||
doc = pdfDocumentFactory.load(tempOutputFile.toFile());
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
|
||||
} finally {
|
||||
|
||||
if (tempOutputFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error deleting temporary output file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String convertURLToFileName(String url) {
|
||||
|
||||
@@ -30,13 +30,13 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/convert")
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
public class ExtractController {
|
||||
public class ExtractCSVController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||
|
||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Extracts a PDF document to csv",
|
||||
summary = "Extracts a CSV document from a PDF",
|
||||
description =
|
||||
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
||||
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
||||
@@ -12,11 +12,11 @@ import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -49,6 +50,13 @@ public class AutoSplitPdfController {
|
||||
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 final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public AutoSplitPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Auto split PDF pages into separate documents",
|
||||
@@ -59,73 +67,93 @@ public class AutoSplitPdfController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
boolean duplexMode = request.isDuplexMode();
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
PDDocument document = null;
|
||||
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||
Path zipFile = null;
|
||||
byte[] data = null;
|
||||
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||
String result = decodeQRCode(bim);
|
||||
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
||||
splitDocuments.add(new PDDocument());
|
||||
try {
|
||||
document = pdfDocumentFactory.load(file.getInputStream());
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||
String result = decodeQRCode(bim);
|
||||
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
||||
splitDocuments.add(new PDDocument());
|
||||
}
|
||||
|
||||
if (!splitDocuments.isEmpty()
|
||||
&& !QR_CONTENT.equals(result)
|
||||
&& !QR_CONTENT_OLD.equals(result)) {
|
||||
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||
} else if (page == 0) {
|
||||
PDDocument firstDocument = new PDDocument();
|
||||
firstDocument.addPage(document.getPage(page));
|
||||
splitDocuments.add(firstDocument);
|
||||
}
|
||||
|
||||
// If duplexMode is true and current page is a divider, then skip next page
|
||||
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!splitDocuments.isEmpty()
|
||||
&& !QR_CONTENT.equals(result)
|
||||
&& !QR_CONTENT_OLD.equals(result)) {
|
||||
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||
} else if (page == 0) {
|
||||
PDDocument firstDocument = new PDDocument();
|
||||
firstDocument.addPage(document.getPage(page));
|
||||
splitDocuments.add(firstDocument);
|
||||
// Remove split documents that have no pages
|
||||
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
||||
|
||||
zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
for (int i = 0; i < splitDocuments.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
PDDocument splitDocument = splitDocuments.get(i);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
// If duplexMode is true and current page is a divider, then skip next page
|
||||
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove split documents that have no pages
|
||||
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
||||
|
||||
for (PDDocument splitDocument : splitDocuments) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
splitDocumentsBoas.add(baos);
|
||||
splitDocument.close();
|
||||
}
|
||||
|
||||
document.close();
|
||||
|
||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
byte[] data;
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("exception", e);
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
} finally {
|
||||
// Clean up resources
|
||||
if (document != null) {
|
||||
try {
|
||||
document.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing main PDDocument", e);
|
||||
}
|
||||
}
|
||||
|
||||
for (PDDocument splitDoc : splitDocuments) {
|
||||
try {
|
||||
splitDoc.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing split PDDocument", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(zipFile);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error deleting temporary zip file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String decodeQRCode(BufferedImage bufferedImage) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -40,6 +42,13 @@ public class BlankPageController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public BlankPageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||
@Operation(
|
||||
summary = "Remove blank pages from a PDF file",
|
||||
@@ -124,7 +133,7 @@ public class BlankPageController {
|
||||
|
||||
public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName)
|
||||
throws IOException {
|
||||
try (PDDocument document = new PDDocument()) {
|
||||
try (PDDocument document = pdfDocumentFactory.createNewDocument()) {
|
||||
|
||||
for (PDPage page : pages) {
|
||||
document.addPage(page);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -32,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
@@ -44,6 +46,13 @@ public class CompressController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public CompressController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||
@Operation(
|
||||
summary = "Optimize PDF file",
|
||||
@@ -258,7 +267,7 @@ public class CompressController {
|
||||
}
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
Path finalFile = tempOutputFile;
|
||||
// Check if optimized file is larger than the original
|
||||
if (pdfBytes.length > inputFileSize) {
|
||||
// Log the occurrence
|
||||
@@ -266,14 +275,15 @@ public class CompressController {
|
||||
"Optimized file is larger than the original. Returning the original file instead.");
|
||||
|
||||
// Read the original file again
|
||||
pdfBytes = Files.readAllBytes(tempInputFile);
|
||||
finalFile = tempInputFile;
|
||||
}
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_Optimized.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
|
||||
@@ -60,8 +60,6 @@ public class ExtractImagesController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String format = request.getFormat();
|
||||
boolean allowDuplicates = request.isAllowDuplicates();
|
||||
System.out.println(
|
||||
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// Determine if multithreading should be used based on PDF size or number of pages
|
||||
@@ -90,22 +88,35 @@ public class ExtractImagesController {
|
||||
// Iterate over each page
|
||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||
PDPage page = document.getPage(pgNum);
|
||||
int pageNum = document.getPages().indexOf(page) + 1;
|
||||
// Submit a task for processing each page
|
||||
Future<Void> future =
|
||||
executor.submit(
|
||||
() -> {
|
||||
extractImagesFromPage(
|
||||
page,
|
||||
format,
|
||||
filename,
|
||||
pageNum,
|
||||
processedImages,
|
||||
zos,
|
||||
allowDuplicates);
|
||||
return null;
|
||||
// Use the page number directly from the iterator, so no need to
|
||||
// calculate manually
|
||||
int pageNum = document.getPages().indexOf(page) + 1;
|
||||
|
||||
try {
|
||||
// Call the image extraction method for each page
|
||||
extractImagesFromPage(
|
||||
page,
|
||||
format,
|
||||
filename,
|
||||
pageNum,
|
||||
processedImages,
|
||||
zos,
|
||||
allowDuplicates);
|
||||
} catch (IOException e) {
|
||||
// Log the error and continue processing other pages
|
||||
logger.error(
|
||||
"Error extracting images from page {}: {}",
|
||||
pageNum,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
return null; // Callable requires a return type
|
||||
});
|
||||
|
||||
// Add the Future object to the list to track completion
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -25,9 +26,8 @@ import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -37,6 +37,13 @@ public class FlattenController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public FlattenController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/flatten")
|
||||
@Operation(
|
||||
summary = "Flatten PDF form fields or full page",
|
||||
@@ -46,7 +53,6 @@ public class FlattenController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
|
||||
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
|
||||
|
||||
if (Boolean.TRUE.equals(flattenOnlyForms)) {
|
||||
@@ -60,7 +66,8 @@ public class FlattenController {
|
||||
// flatten whole page aka convert each page to image and readd it (making text
|
||||
// unselectable)
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
PDDocument newDocument = new PDDocument();
|
||||
PDDocument newDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document);
|
||||
int numPages = document.getNumberOfPages();
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
try {
|
||||
@@ -80,7 +87,6 @@ public class FlattenController {
|
||||
logger.error("exception", e);
|
||||
}
|
||||
}
|
||||
PdfUtils.setMetadataToPdf(newDocument, metadata);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -28,6 +29,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@@ -52,6 +54,13 @@ public class OCRController {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||
@Operation(
|
||||
summary = "Process a PDF file with OCR",
|
||||
@@ -175,7 +184,7 @@ public class OCRController {
|
||||
tempOutputFile = tempPdfWithoutImages;
|
||||
}
|
||||
// Read the OCR processed PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
||||
|
||||
// Return the OCR processed PDF as a response
|
||||
String outputFilename =
|
||||
@@ -196,7 +205,13 @@ public class OCRController {
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
Files.copy(tempOutputFile, zipOut);
|
||||
try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = pdfInputStream.read(buffer)) != -1) {
|
||||
zipOut.write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
zipOut.closeEntry();
|
||||
|
||||
// Add text file to the zip
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -27,6 +29,13 @@ public class OverlayImageController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public OverlayImageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||
@Operation(
|
||||
summary = "Overlay image onto a PDF file",
|
||||
@@ -41,7 +50,9 @@ public class OverlayImageController {
|
||||
try {
|
||||
byte[] pdfBytes = pdfFile.getBytes();
|
||||
byte[] imageBytes = imageFile.getBytes();
|
||||
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage);
|
||||
byte[] result =
|
||||
PdfUtils.overlayImage(
|
||||
pdfDocumentFactory, pdfBytes, imageBytes, x, y, everyPage);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
@@ -13,6 +12,7 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
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.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -36,6 +37,13 @@ public class PageNumbersController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public PageNumbersController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Add page numbers to a PDF document",
|
||||
@@ -52,7 +60,7 @@ public class PageNumbersController {
|
||||
String customText = request.getCustomText();
|
||||
int pageNumber = startingNumber;
|
||||
byte[] fileBytes = file.getBytes();
|
||||
PDDocument document = Loader.loadPDF(fileBytes);
|
||||
PDDocument document = pdfDocumentFactory.load(fileBytes);
|
||||
float font_size = request.getFontSize();
|
||||
String font_type = request.getFontType();
|
||||
float marginFactor;
|
||||
|
||||
@@ -26,11 +26,13 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
@Slf4j
|
||||
public class PrintFileController {
|
||||
|
||||
// TODO
|
||||
@@ -59,7 +61,7 @@ public class PrintFileController {
|
||||
new IllegalArgumentException(
|
||||
"No matching printer found"));
|
||||
|
||||
System.out.println("Selected Printer: " + selectedService.getName());
|
||||
log.info("Selected Printer: " + selectedService.getName());
|
||||
|
||||
if ("application/pdf".equals(contentType)) {
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@@ -31,6 +33,13 @@ public class RepairController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public RepairController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
||||
@Operation(
|
||||
summary = "Repair a PDF file",
|
||||
@@ -58,7 +67,7 @@ public class RepairController {
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
||||
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
public class ReplaceAndInvertColorController {
|
||||
|
||||
private ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||
|
||||
@Autowired
|
||||
public ReplaceAndInvertColorController(
|
||||
ReplaceAndInvertColorService replaceAndInvertColorService) {
|
||||
this.replaceAndInvertColorService = replaceAndInvertColorService;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/replace-invert-pdf")
|
||||
@Operation(
|
||||
summary = "Replace-Invert Color PDF",
|
||||
description =
|
||||
"This endpoint accepts a PDF file and option of invert all colors or replace text and background colors. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<InputStreamResource> replaceAndInvertColor(
|
||||
@ModelAttribute ReplaceAndInvertColorRequest replaceAndInvertColorRequest)
|
||||
throws IOException {
|
||||
|
||||
InputStreamResource resource =
|
||||
replaceAndInvertColorService.replaceAndInvertColor(
|
||||
replaceAndInvertColorRequest.getFileInput(),
|
||||
replaceAndInvertColorRequest.getReplaceAndInvertOption(),
|
||||
replaceAndInvertColorRequest.getHighContrastColorCombination(),
|
||||
replaceAndInvertColorRequest.getBackGroundColor(),
|
||||
replaceAndInvertColorRequest.getTextColor());
|
||||
|
||||
// Return the modified PDF as a downloadable file
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=inverted.pdf")
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(resource);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||
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.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -75,7 +76,8 @@ public class ShowJavascript {
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
script.getBytes(StandardCharsets.UTF_8),
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js");
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js",
|
||||
MediaType.TEXT_PLAIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
@@ -25,6 +24,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddStampRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -45,6 +46,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class StampController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public StampController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||
@Operation(
|
||||
summary = "Add stamp to a PDF file",
|
||||
@@ -86,7 +94,7 @@ public class StampController {
|
||||
}
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
|
||||
|
||||
|
||||
@@ -2,4 +2,6 @@ package stirling.software.SPDF.controller.api.pipeline;
|
||||
|
||||
public interface UserServiceInterface {
|
||||
String getApiKeyForUser(String username);
|
||||
|
||||
String getCurrentUsername();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||
@@ -35,6 +34,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||
import org.bouncycastle.pkcs.PKCSException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -47,6 +47,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -71,6 +72,13 @@ 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",
|
||||
@@ -122,7 +130,7 @@ public class CertSignController {
|
||||
|
||||
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
sign(pdf.getBytes(), baos, createSignature, name, location, reason);
|
||||
sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason);
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
baos,
|
||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||
@@ -130,13 +138,14 @@ public class CertSignController {
|
||||
}
|
||||
|
||||
private static void sign(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
byte[] input,
|
||||
OutputStream output,
|
||||
CreateSignature instance,
|
||||
String name,
|
||||
String location,
|
||||
String reason) {
|
||||
try (PDDocument doc = Loader.loadPDF(input)) {
|
||||
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
||||
PDSignature signature = new PDSignature();
|
||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
||||
|
||||
@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
|
||||
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -30,6 +31,13 @@ public class PasswordController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public PasswordController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||
@Operation(
|
||||
summary = "Remove password from a PDF file",
|
||||
@@ -39,8 +47,7 @@ public class PasswordController {
|
||||
throws IOException {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String password = request.getPassword();
|
||||
|
||||
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
|
||||
PDDocument document = pdfDocumentFactory.load(fileInput, password);
|
||||
document.setAllSecurityToBeRemoved(true);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
@@ -69,7 +76,7 @@ public class PasswordController {
|
||||
boolean canPrint = request.isCanPrint();
|
||||
boolean canPrintFaithful = request.isCanPrintFaithful();
|
||||
|
||||
PDDocument document = Loader.loadPDF(fileInput.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(fileInput);
|
||||
AccessPermission ap = new AccessPermission();
|
||||
ap.setCanAssembleDocument(!canAssembleDocument);
|
||||
ap.setCanExtractContent(!canExtractContent);
|
||||
|
||||
@@ -5,12 +5,12 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||
import stirling.software.SPDF.pdf.TextFinder;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -35,6 +36,13 @@ public class RedactController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public RedactController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Redacts listOfText in a PDF document",
|
||||
@@ -50,10 +58,8 @@ public class RedactController {
|
||||
float customPadding = request.getCustomPadding();
|
||||
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||
|
||||
System.out.println(listOfTextString);
|
||||
String[] listOfText = listOfTextString.split("\n");
|
||||
byte[] bytes = file.getBytes();
|
||||
PDDocument document = Loader.loadPDF(bytes);
|
||||
PDDocument document = pdfDocumentFactory.load(file);
|
||||
|
||||
Color redactColor;
|
||||
try {
|
||||
@@ -68,7 +74,6 @@ public class RedactController {
|
||||
|
||||
for (String text : listOfText) {
|
||||
text = text.trim();
|
||||
System.out.println(text);
|
||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||
redactFoundText(document, foundTexts, customPadding, redactColor);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
@@ -12,6 +10,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -24,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -33,6 +33,13 @@ public class RemoveCertSignController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public RemoveCertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
||||
@Operation(
|
||||
summary = "Remove digital signature from PDF",
|
||||
@@ -42,14 +49,8 @@ public class RemoveCertSignController {
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
|
||||
// Convert MultipartFile to byte[]
|
||||
byte[] pdfBytes = pdf.getBytes();
|
||||
|
||||
// Create a ByteArrayOutputStream to hold the resulting PDF
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
PDDocument document = pdfDocumentFactory.load(pdf);
|
||||
|
||||
// Get the document catalog
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
@@ -67,14 +68,9 @@ public class RemoveCertSignController {
|
||||
acroForm.flatten(fieldsToRemove, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the modified document to the ByteArrayOutputStream
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
// Return the modified PDF as a response
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
baos,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||
+ "_unsigned.pdf");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSDictionary;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -21,6 +20,7 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -40,6 +41,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class SanitizeController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public SanitizeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||
@Operation(
|
||||
summary = "Sanitize a PDF file",
|
||||
@@ -54,7 +62,7 @@ public class SanitizeController {
|
||||
boolean removeLinks = request.isRemoveLinks();
|
||||
boolean removeFonts = request.isRemoveFonts();
|
||||
|
||||
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(inputFile);
|
||||
if (removeJavaScript) {
|
||||
sanitizeJavaScript(document);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.nio.file.Files;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
@@ -23,6 +22,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -36,6 +36,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -44,6 +45,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class WatermarkController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public WatermarkController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||
@Operation(
|
||||
summary = "Add watermark to a PDF file",
|
||||
@@ -64,7 +72,7 @@ public class WatermarkController {
|
||||
boolean convertPdfToImage = request.isConvertPDFToImage();
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||
|
||||
// Create a page in the document
|
||||
for (PDPage page : document.getPages()) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class AccountWebController {
|
||||
|
||||
Map<String, String> providerList = new HashMap<>();
|
||||
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
if (oauth != null) {
|
||||
if (oauth.isSettingsValid()) {
|
||||
providerList.put("oidc", oauth.getProvider());
|
||||
@@ -82,7 +82,7 @@ public class AccountWebController {
|
||||
|
||||
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
|
||||
model.addAttribute(
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled());
|
||||
|
||||
model.addAttribute("currentPage", "login");
|
||||
|
||||
@@ -345,7 +345,7 @@ public class AccountWebController {
|
||||
// Retrieve username and other attributes
|
||||
username =
|
||||
userDetails.getAttribute(
|
||||
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername());
|
||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||
// Add oAuth2 Login attributes to the model
|
||||
model.addAttribute("oAuth2Login", true);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
public class ConverterWebController {
|
||||
|
||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||
@GetMapping("/book-to-pdf")
|
||||
@Hidden
|
||||
public String convertBookToPdfForm(Model model) {
|
||||
@@ -60,7 +60,7 @@ public class ConverterWebController {
|
||||
|
||||
// PDF TO......
|
||||
|
||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||
@GetMapping("/pdf-to-book")
|
||||
@Hidden
|
||||
public String convertPdfToBookForm(Model model) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user