Merge pull request #2427 from Stirling-Tools/testStuff

X-API-key to X-API-KEY and enable CSRF protection for all users
This commit is contained in:
Anthony Stirling 2024-12-11 21:52:57 +00:00 committed by GitHub
commit 026fe8150d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 540 additions and 287 deletions

View File

@ -405,7 +405,7 @@ To access your account settings, go to Account Settings in the settings cog menu
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future. To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
For API usage, you must provide a header with `X-API-Key` and the associated API key for that user. For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user.
## FAQ ## FAQ

View File

@ -15,6 +15,10 @@ import shutil
import re import re
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
API_HEADERS = {
'X-API-KEY': '123456789'
}
######### #########
# GIVEN # # GIVEN #
######### #########
@ -227,7 +231,7 @@ def save_generated_pdf(context, filename):
def step_send_get_request(context, endpoint): def step_send_get_request(context, endpoint):
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
full_url = f"{base_url}{endpoint}" full_url = f"{base_url}{endpoint}"
response = requests.get(full_url) response = requests.get(full_url, headers=API_HEADERS)
context.response = response context.response = response
@when('I send a GET request to "{endpoint}" with parameters') @when('I send a GET request to "{endpoint}" with parameters')
@ -235,7 +239,7 @@ def step_send_get_request_with_params(context, endpoint):
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
params = {row['parameter']: row['value'] for row in context.table} params = {row['parameter']: row['value'] for row in context.table}
full_url = f"{base_url}{endpoint}" full_url = f"{base_url}{endpoint}"
response = requests.get(full_url, params=params) response = requests.get(full_url, params=params, headers=API_HEADERS)
context.response = response context.response = response
@when('I send the API request to the endpoint "{endpoint}"') @when('I send the API request to the endpoint "{endpoint}"')
@ -256,7 +260,7 @@ def step_send_api_request(context, endpoint):
print(f"form_data {file.name} with {mime_type}") print(f"form_data {file.name} with {mime_type}")
form_data.append((key, (file.name, file, mime_type))) form_data.append((key, (file.name, file, mime_type)))
response = requests.post(url, files=form_data) response = requests.post(url, files=form_data, headers=API_HEADERS)
context.response = response context.response = response
######## ########

View File

@ -0,0 +1,34 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat
image: stirlingtools/stirling-pdf:latest-fat
deploy:
resources:
limits:
memory: 4G
healthcheck:
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
PUID: 1002
PGID: 1002
UMASK: "022"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
restart: on-failure:5

View File

@ -28,7 +28,7 @@ public class LicenseKeyChecker {
this.checkLicense(); this.checkLicense();
} }
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds @Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() { public void checkLicensePeriodically() {
checkLicense(); checkLicense();
} }

View File

@ -1,11 +1,14 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import java.util.UUID; import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import io.micrometer.common.util.StringUtils; import io.micrometer.common.util.StringUtils;
@ -23,6 +26,18 @@ public class InitialSetup {
@Autowired private ApplicationProperties applicationProperties; @Autowired private ApplicationProperties applicationProperties;
@PostConstruct @PostConstruct
public void init() throws IOException {
initUUIDKey();
initSecretKey();
initEnableCSRFSecurity();
initLegalUrls();
initSetAppVersion();
}
public void initUUIDKey() throws IOException { public void initUUIDKey() throws IOException {
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID(); String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
if (!GeneralUtils.isValidUUID(uuid)) { if (!GeneralUtils.isValidUUID(uuid)) {
@ -32,7 +47,6 @@ public class InitialSetup {
} }
} }
@PostConstruct
public void initSecretKey() throws IOException { public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (!GeneralUtils.isValidUUID(secretKey)) { if (!GeneralUtils.isValidUUID(secretKey)) {
@ -42,13 +56,24 @@ public class InitialSetup {
} }
} }
@PostConstruct public void initEnableCSRFSecurity() throws IOException {
if (GeneralUtils.isVersionHigher(
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
if (!csrf) {
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
applicationProperties.getSecurity().setCsrfDisabled(false);
}
}
}
public void initLegalUrls() throws IOException { public void initLegalUrls() throws IOException {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl); GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }
@ -56,8 +81,23 @@ public class InitialSetup {
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy(); String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
if (StringUtils.isEmpty(privacyUrl)) { if (StringUtils.isEmpty(privacyUrl)) {
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy"; String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl); GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl); applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
} }
} }
public void initSetAppVersion() throws IOException {
String appVersion = "0.0.0";
Resource resource = new ClassPathResource("version.properties");
Properties props = new Properties();
try {
props.load(resource.getInputStream());
appVersion = props.getProperty("version");
} catch (Exception e) {
}
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
}
} }

View File

@ -75,5 +75,7 @@ public class InitialSecuritySetup {
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId()); log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
} }
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
System.out.println(applicationProperties.getSecurity().getCustomGlobalAPIKey());
} }
} }

View File

@ -99,7 +99,7 @@ public class SecurityConfiguration {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled()) { if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
} }
@ -116,7 +116,7 @@ public class SecurityConfiguration {
csrf -> csrf ->
csrf.ignoringRequestMatchers( csrf.ignoringRequestMatchers(
request -> { request -> {
String apiKey = request.getHeader("X-API-Key"); String apiKey = request.getHeader("X-API-KEY");
// If there's no API key, don't ignore CSRF // If there's no API key, don't ignore CSRF
// (return false) // (return false)
@ -289,17 +289,17 @@ public class SecurityConfiguration {
} }
} else { } else {
if (!applicationProperties.getSecurity().getCsrfDisabled()) { // if (!applicationProperties.getSecurity().getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo = // CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse(); // CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = // CsrfTokenRequestAttributeHandler requestHandler =
new CsrfTokenRequestAttributeHandler(); // new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName(null); // requestHandler.setCsrfRequestAttributeName(null);
http.csrf( // http.csrf(
csrf -> // csrf ->
csrf.csrfTokenRepository(cookieRepo) // csrf.csrfTokenRepository(cookieRepo)
.csrfTokenRequestHandler(requestHandler)); // .csrfTokenRequestHandler(requestHandler));
} // }
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
} }

View File

@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// Check for API key in the request headers if no authentication exists // Check for API key in the request headers if no authentication exists
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
String apiKey = request.getHeader("X-API-Key"); String apiKey = request.getHeader("X-API-KEY");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
try { try {
// Use API key to authenticate. This requires you to have an authentication // Use API key to authenticate. This requires you to have an authentication

View File

@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
String identifier = null; String identifier = null;
// Check for API key in the request headers // Check for API key in the request headers
String apiKey = request.getHeader("X-API-Key"); String apiKey = request.getHeader("X-API-KEY");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
identifier = identifier =
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
Role userRole = Role userRole =
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-Key") != null) { if (request.getHeader("X-API-KEY") != null) {
// It's an API call // It's an API call
processRequest( processRequest(
userRole.getApiCallsPerDay(), userRole.getApiCallsPerDay(),

View File

@ -390,6 +390,37 @@ public class UserService implements UserServiceInterface {
} }
} }
@Transactional
public void syncCustomApiUser(String customApiKey) throws IOException {
if (customApiKey == null || customApiKey.trim().length() == 0) {
return;
}
String username = "CUSTOM_API_USER";
Optional<User> existingUser = findByUsernameIgnoreCase(username);
if (!existingUser.isPresent()) {
// Create new user with API role
User user = new User();
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString());
user.setEnabled(true);
user.setFirstLogin(false);
user.setAuthenticationType(AuthenticationType.WEB);
user.setApiKey(customApiKey);
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
userRepository.save(user);
databaseBackupHelper.exportDatabase();
} else {
// Update API key if it has changed
User user = existingUser.get();
if (!customApiKey.equals(user.getApiKey())) {
user.setApiKey(customApiKey);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
}
}
@Override @Override
public long getTotalUsersCount() { public long getTotalUsersCount() {
return userRepository.count(); return userRepository.count();

View File

@ -52,84 +52,115 @@ public class SplitPDFController {
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO") "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request) public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = null;
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document); Path zipFile = null;
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) { try {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) { MultipartFile file = request.getFileInput();
for (int i = previousPageNumber; i <= splitPoint; i++) { String pages = request.getPageNumbers();
PDPage page = document.getPage(i); // open the pdf document
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i); document = Loader.loadPDF(file.getBytes());
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i);
}
previousPageNumber = splitPoint + 1;
// Transfer metadata to split pdf
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
throw e;
} }
previousPageNumber = splitPoint + 1; }
// Transfer metadata to split pdf // closing the original document
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata); document.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); zipFile = Files.createTempFile("split_documents", ".zip");
splitDocument.save(baos);
splitDocumentsBoas.add(baos); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed splitting documents and saving them", e); logger.error("Failed writing to zip", e);
throw e; throw e;
} }
}
// closing the original document logger.info(
document.close(); "Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
Path zipFile = Files.createTempFile("split_documents", ".zip"); // return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
String filename = } finally {
Filenames.toSimpleFileName(file.getOriginalFilename()) try {
.replaceFirst("[.][^.]+$", ""); // Close the main document
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { if (document != null) {
// loop through the split documents and write them to the zip file document.close();
for (int i = 0; i < splitDocumentsBoas.size(); i++) { }
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip // Close all ByteArrayOutputStreams
ZipEntry pdfEntry = new ZipEntry(fileName); for (ByteArrayOutputStream baos : splitDocumentsBoas) {
zipOut.putNextEntry(pdfEntry); if (baos != null) {
zipOut.write(pdf); baos.close();
zipOut.closeEntry(); }
}
logger.info("Wrote split document {} to zip file", fileName); // Delete temporary zip file
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
logger.error("Error while cleaning up resources", e);
} }
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
} }
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
// return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} }
} }

View File

@ -59,70 +59,86 @@ public class SplitPdfByChaptersController {
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request) public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean includeMetadata = request.getIncludeMetadata(); PDDocument sourceDocument = null;
Integer bookmarkLevel = Path zipFile = null;
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
}
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try { try {
bookmarks = boolean includeMetadata = request.getIncludeMetadata();
extractOutlineItems( Integer bookmarkLevel =
sourceDocument, request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
outline.getFirstChild(), if (bookmarkLevel < 0) {
bookmarks, return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
outline.getFirstChild().getNextSibling(), }
0, sourceDocument = Loader.loadPDF(file.getBytes());
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) { PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError() if (outline == null) {
.body("Unable to extract outline items".getBytes()); logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) {
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
try {
if (sourceDocument != null) {
sourceDocument.close();
}
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
logger.error("Error while cleaning up resources", e);
}
} }
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} }
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) { private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {

View File

@ -105,15 +105,13 @@ public class SplitPdfBySectionsController {
if (sectionNum == horiz * verti) pageNum++; if (sectionNum == horiz * verti) pageNum++;
} }
} catch (Exception e) {
logger.error("exception", e);
} finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);
} }
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} }
public List<PDDocument> splitPdfPages( public List<PDDocument> splitPdfPages(

View File

@ -65,112 +65,137 @@ public class ConvertImgPDFController {
String colorType = request.getColorType(); String colorType = request.getColorType();
String dpi = request.getDpi(); String dpi = request.getDpi();
byte[] pdfBytes = file.getBytes(); Path tempFile = null;
ImageType colorTypeResult = ImageType.RGB; Path tempOutputDir = null;
if ("greyscale".equals(colorType)) { Path tempPdfPath = null;
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
byte[] result = null; byte[] result = null;
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
result = try {
PdfUtils.convertFromPdf( byte[] pdfBytes = file.getBytes();
pdfBytes, ImageType colorTypeResult = ImageType.RGB;
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(), if ("greyscale".equals(colorType)) {
colorTypeResult, colorTypeResult = ImageType.GRAY;
singleImage, } else if ("blackwhite".equals(colorType)) {
Integer.valueOf(dpi), colorTypeResult = ImageType.BINARY;
filename);
if (result == null || result.length == 0) {
logger.error("resultant bytes for {} is null, error converting ", filename);
}
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed. Required for WebP conversion.");
} else if ("webp".equalsIgnoreCase(imageFormat)
&& CheckProgramInstall.isPythonAvailable()) {
// Write the output stream to a temp file
Path tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
} }
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); result =
PdfUtils.convertFromPdf(
List<String> command = new ArrayList<>(); pdfBytes,
command.add(pythonVersion); "webp".equalsIgnoreCase(imageFormat)
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion ? "png"
: imageFormat.toUpperCase(),
// Create a temporary directory for the output WebP files colorTypeResult,
Path tempOutputDir = Files.createTempDirectory("webp_output"); singleImage,
if (singleImage) { Integer.valueOf(dpi),
// Run the Python script to convert PNG to WebP filename);
command.add(tempFile.toString()); if (result == null || result.length == 0) {
command.add(tempOutputDir.toString()); logger.error("resultant bytes for {} is null, error converting ", filename);
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
} }
command.add("--dpi"); if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
command.add(dpi); throw new IOException("Python is not installed. Required for WebP conversion.");
ProcessExecutorResult resultProcess = } else if ("webp".equalsIgnoreCase(imageFormat)
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) && CheckProgramInstall.isPythonAvailable()) {
.runCommandWithOutputHandling(command); // Write the output stream to a temp file
tempFile = Files.createTempFile("temp_png", ".png");
// Find all WebP files in the output directory try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
List<Path> webpFiles = fos.write(result);
Files.walk(tempOutputDir) fos.flush();
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
} }
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) { String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat)); List<String> command = new ArrayList<>();
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType); command.add(pythonVersion);
} else { command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse( // Create a temporary directory for the output WebP files
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM); tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException(
"No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) {
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
} else {
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse(
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
}
} finally {
try {
// Clean up temporary files
if (tempFile != null) {
Files.deleteIfExists(tempFile);
}
if (tempPdfPath != null) {
Files.deleteIfExists(tempPdfPath);
}
if (tempOutputDir != null) {
FileUtils.deleteDirectory(tempOutputDir.toFile());
}
} catch (Exception e) {
logger.error("Error cleaning up temporary files", e);
}
} }
} }

View File

@ -87,7 +87,7 @@ public class OCRController {
Files.createDirectories(tempOutputDir); Files.createDirectories(tempOutputDir);
Files.createDirectories(tempImagesDir); Files.createDirectories(tempImagesDir);
Process process = null;
try { try {
// Save input file // Save input file
inputFile.transferTo(tempInputFile.toFile()); inputFile.transferTo(tempInputFile.toFile());
@ -139,7 +139,7 @@ public class OCRController {
command.add("pdf"); // Always output PDF command.add("pdf"); // Always output PDF
ProcessBuilder pb = new ProcessBuilder(command); ProcessBuilder pb = new ProcessBuilder(command);
Process process = pb.start(); process = pb.start();
// Capture any error output // Capture any error output
try (BufferedReader reader = try (BufferedReader reader =
@ -188,6 +188,10 @@ public class OCRController {
.body(pdfContent); .body(pdfContent);
} finally { } finally {
if (process != null) {
process.destroy();
}
// Clean up temporary files // Clean up temporary files
deleteDirectory(tempDir); deleteDirectory(tempDir);
} }

View File

@ -221,7 +221,7 @@ public class PipelineProcessor {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
String apiKey = getApiKeyForUser(); String apiKey = getApiKeyForUser();
headers.add("X-API-Key", apiKey); headers.add("X-API-KEY", apiKey);
headers.setContentType(MediaType.MULTIPART_FORM_DATA); headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// Create HttpEntity with the body and headers // Create HttpEntity with the body and headers

View File

@ -595,7 +595,9 @@ public class GetInfoOnPDF {
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility())); permissionsNode.put(
"Extracting for accessibility",
getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm())); permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));

View File

@ -92,20 +92,29 @@ public class ValidateSignatureController {
SignerInformationStore signerStore = signedData.getSignerInfos(); SignerInformationStore signerStore = signedData.getSignerInfos();
for (SignerInformation signer : signerStore.getSigners()) { for (SignerInformation signer : signerStore.getSigners()) {
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next(); X509CertificateHolder certHolder =
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder); (X509CertificateHolder)
certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert =
new JcaX509CertificateConverter().getCertificate(certHolder);
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); boolean isValid =
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
result.setValid(isValid); result.setValid(isValid);
// Additional validations // Additional validations
result.setChainValid(customCert != null result.setChainValid(
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert) customCert != null
: certValidationService.validateCertificateChain(cert)); ? certValidationService
.validateCertificateChainWithCustomCert(
cert, customCert)
: certValidationService.validateCertificateChain(cert));
result.setTrustValid(customCert != null result.setTrustValid(
? certValidationService.validateTrustWithCustomCert(cert, customCert) customCert != null
: certValidationService.validateTrustStore(cert)); ? certValidationService.validateTrustWithCustomCert(
cert, customCert)
: certValidationService.validateTrustStore(cert));
result.setNotRevoked(!certValidationService.isRevoked(cert)); result.setNotRevoked(!certValidationService.isRevoked(cert));
result.setNotExpired(!cert.getNotAfter().before(new Date())); result.setNotExpired(!cert.getNotAfter().before(new Date()));
@ -123,17 +132,18 @@ public class ValidateSignatureController {
result.setValidFrom(cert.getNotBefore().toString()); result.setValidFrom(cert.getNotBefore().toString());
result.setValidUntil(cert.getNotAfter().toString()); result.setValidUntil(cert.getNotAfter().toString());
result.setSignatureAlgorithm(cert.getSigAlgName()); result.setSignatureAlgorithm(cert.getSigAlgName());
// Get key size (if possible) // Get key size (if possible)
try { try {
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength()); result.setKeySize(
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
} catch (Exception e) { } catch (Exception e) {
// If not RSA or error, set to 0 // If not RSA or error, set to 0
result.setKeySize(0); result.setKeySize(0);
} }
result.setVersion(String.valueOf(cert.getVersion())); result.setVersion(String.valueOf(cert.getVersion()));
// Set key usage // Set key usage
List<String> keyUsages = new ArrayList<>(); List<String> keyUsages = new ArrayList<>();
boolean[] keyUsageFlags = cert.getKeyUsage(); boolean[] keyUsageFlags = cert.getKeyUsage();
@ -150,9 +160,11 @@ public class ValidateSignatureController {
} }
} }
result.setKeyUsages(keyUsages); result.setKeyUsages(keyUsages);
// Check if self-signed // Check if self-signed
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())); result.setSelfSigned(
cert.getSubjectX500Principal()
.equals(cert.getIssuerX500Principal()));
} }
} catch (Exception e) { } catch (Exception e) {
result.setValid(false); result.setValid(false);

View File

@ -73,6 +73,7 @@ public class ApplicationProperties {
private int loginAttemptCount; private int loginAttemptCount;
private long loginResetTimeMinutes; private long loginResetTimeMinutes;
private String loginMethod = "all"; private String loginMethod = "all";
private String customGlobalAPIKey;
public Boolean isAltLogin() { public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled(); return saml2.getEnabled() || oauth2.getEnabled();
@ -285,6 +286,7 @@ public class ApplicationProperties {
public static class AutomaticallyGenerated { public static class AutomaticallyGenerated {
@ToString.Exclude private String key; @ToString.Exclude private String key;
private String UUID; private String UUID;
private String appVersion;
} }
@Data @Data

View File

@ -16,16 +16,15 @@ public class SignatureValidationResult {
private boolean trustValid; private boolean trustValid;
private boolean notExpired; private boolean notExpired;
private boolean notRevoked; private boolean notRevoked;
private String issuerDN; // Certificate issuer's Distinguished Name private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date private String validUntil; // Certificate validity end date
private String signatureAlgorithm;// Algorithm used for signing private String signatureAlgorithm; // Algorithm used for signing
private int keySize; // Key size in bits private int keySize; // Key size in bits
private String version; // Certificate version private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed private boolean isSelfSigned; // Whether the certificate is self-signed
} }

View File

@ -1,6 +1,5 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import io.github.pixee.security.BoundedLineReader;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -24,6 +23,8 @@ import java.util.Set;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import io.github.pixee.security.BoundedLineReader;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@Service @Service

View File

@ -289,6 +289,10 @@ public class GeneralUtils {
saveKeyToConfig(id, key, true); saveKeyToConfig(id, key, true);
} }
public static void saveKeyToConfig(String id, boolean key) throws IOException {
saveKeyToConfig(id, key, true);
}
public static void saveKeyToConfig(String id, String key, boolean autoGenerated) public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
throws IOException { throws IOException {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
@ -307,6 +311,24 @@ public class GeneralUtils {
settingsYml.save(); settingsYml.save();
} }
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
throws IOException {
Path path = Paths.get("configs", "settings.yml");
final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml =
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
yamlOptionssettingsYml.setSplitLines(false);
settingsYml.loadWithComments();
YamlFileWrapper writer = settingsYml.path(id).set(key);
if (autoGenerated) {
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
}
settingsYml.save();
}
public static String generateMachineFingerprint() { public static String generateMachineFingerprint() {
try { try {
// Get the MAC address // Get the MAC address
@ -349,4 +371,33 @@ public class GeneralUtils {
return "GenericID"; return "GenericID";
} }
} }
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
if (currentVersion == null || compareVersion == null) {
return false;
}
// Split versions into components
String[] current = currentVersion.split("\\.");
String[] compare = compareVersion.split("\\.");
// Get the length of the shorter version array
int length = Math.min(current.length, compare.length);
// Compare each component
for (int i = 0; i < length; i++) {
int currentPart = Integer.parseInt(current[i]);
int comparePart = Integer.parseInt(compare[i]);
if (currentPart > comparePart) {
return true;
}
if (currentPart < comparePart) {
return false;
}
}
// If all components so far are equal, the longer version is considered higher
return current.length > compare.length;
}
} }

View File

@ -13,7 +13,7 @@
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2) loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
@ -102,7 +102,8 @@ metrics:
AutomaticallyGenerated: AutomaticallyGenerated:
key: example key: example
UUID: example UUID: example
appVersion: 0.35.0
processExecutor: processExecutor:
sessionLimit: # Process executor instances limits sessionLimit: # Process executor instances limits
libreOfficeSessionLimit: 1 libreOfficeSessionLimit: 1

View File

@ -104,7 +104,7 @@ main() {
# run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml" # run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml"
# docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down # docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down
run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/docker-compose-latest-fat-security.yml" run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/test_cicd.yml"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
cd cucumber cd cucumber
if python -m behave; then if python -m behave; then