update #5

Open
mdebray wants to merge 231 commits from update into main
74 changed files with 749 additions and 772 deletions
Showing only changes of commit 8c01425eee - Show all commits

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Security

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Security

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite-Security

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite

View file

@ -1,4 +1,3 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF

View file

@ -0,0 +1,25 @@
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;
@Bean(name = "RunningEE")
public boolean runningEnterpriseEdition() {
// TODO: Implement EE detection
return false;
}
}

View file

@ -31,7 +31,6 @@ public class SPdfApplication {
@Autowired private Environment env;
@Autowired ApplicationProperties applicationProperties;
private static String serverPortStatic;

View file

@ -135,4 +135,29 @@ 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();
}
}

View file

@ -18,7 +18,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;
}

View file

@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component
public class MetricsFilter extends OncePerRequestFilter {
@ -30,32 +31,16 @@ 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)) {
Counter counter =
Counter.builder("http.requests")
.tag("uri", uri)
.tag("session", request.getSession().getId())
.tag("method", request.getMethod())
.tag("uri", uri)
.register(meterRegistry);
counter.increment();
// System.out.println("Counted");
}
filterChain.doFilter(request, response);

View file

@ -0,0 +1,139 @@
package stirling.software.SPDF.config;
import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PdfMetadata;
@Service
public class PdfMetadataService {
private static PdfMetadataService instance;
private final ApplicationProperties applicationProperties;
private final String appVersion;
private final UserServiceInterface userService;
@Autowired
public PdfMetadataService(
ApplicationProperties applicationProperties,
@Qualifier("appVersion") String appVersion,
@Autowired(required = false) UserServiceInterface userService) {
this.applicationProperties = applicationProperties;
this.appVersion = appVersion;
this.userService = userService;
}
@PostConstruct
public void init() {
instance = this;
}
// Static methods for easy access
public static PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
return instance.extractMetadataFromPdfInstance(pdf);
}
public static void setDefaultMetadata(PDDocument pdf) {
instance.setDefaultMetadataInstance(pdf);
}
public static void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
instance.setMetadataToPdfInstance(pdf, pdfMetadata);
}
public static void setMetadataToPdf(
PDDocument pdf, PdfMetadata pdfMetadata, boolean newlyCreated) {
instance.setMetadataToPdfInstance(pdf, pdfMetadata, newlyCreated);
}
// Instance methods
private PdfMetadata extractMetadataFromPdfInstance(PDDocument pdf) {
return PdfMetadata.builder()
.author(pdf.getDocumentInformation().getAuthor())
.producer(pdf.getDocumentInformation().getProducer())
.title(pdf.getDocumentInformation().getTitle())
.creator(pdf.getDocumentInformation().getCreator())
.subject(pdf.getDocumentInformation().getSubject())
.keywords(pdf.getDocumentInformation().getKeywords())
.creationDate(pdf.getDocumentInformation().getCreationDate())
.modificationDate(pdf.getDocumentInformation().getModificationDate())
.build();
}
private void setDefaultMetadataInstance(PDDocument pdf) {
PdfMetadata metadata = extractMetadataFromPdfInstance(pdf);
setMetadataToPdfInstance(pdf, metadata);
}
private void setMetadataToPdfInstance(PDDocument pdf, PdfMetadata pdfMetadata) {
setMetadataToPdfInstance(pdf, pdfMetadata, true);
}
private void setMetadataToPdfInstance(
PDDocument pdf, PdfMetadata pdfMetadata, boolean newlyCreated) {
if (newlyCreated || pdfMetadata.getCreationDate() == null) {
setNewDocumentMetadata(pdf, pdfMetadata);
}
setCommonMetadata(pdf, pdfMetadata);
}
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
String title = pdfMetadata.getTitle();
String creator = "Stirling-PDF";
// if (applicationProperties
// .getEnterpriseEdition()
// .getCustomMetadata()
// .isAutoUpdateMetadata()) {
// producer =
//
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer();
// creator =
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
// title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle();
// if ("{filename}".equals(title)) {
// title = "Filename"; // Replace with actual filename logic
// } else if ("{unchanged}".equals(title)) {
// title = pdfMetadata.getTitle(); // Keep the original title
// }
// }
pdf.getDocumentInformation().setTitle(title);
pdf.getDocumentInformation().setCreator(creator + " " + appVersion);
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
}
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
String producer = "Stirling-PDF";
pdf.getDocumentInformation().setProducer(producer + " " + appVersion);
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
String author = pdfMetadata.getAuthor();
// if (applicationProperties
// .getEnterpriseEdition()
// .getCustomMetadata()
// .isAutoUpdateMetadata()) {
// author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
// if (userService != null) {
// author = author.replace("username", userService.getCurrentUsername());
// }
// }
pdf.getDocumentInformation().setAuthor(author);
}
}

View file

@ -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;
}

View file

@ -152,8 +152,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()
@ -222,7 +222,7 @@ public class SecurityConfiguration {
}
private Optional<ClientRegistration> googleClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) {
return Optional.empty();
}
@ -251,7 +251,7 @@ public class SecurityConfiguration {
}
private Optional<ClientRegistration> keycloakClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) {
return Optional.empty();
}
@ -275,7 +275,7 @@ public class SecurityConfiguration {
}
private Optional<ClientRegistration> githubClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) {
return Optional.empty();
}
@ -304,7 +304,7 @@ public class SecurityConfiguration {
}
private Optional<ClientRegistration> oidcClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null
|| oauth.getIssuer() == null
|| oauth.getIssuer().isEmpty()
@ -352,7 +352,7 @@ public class SecurityConfiguration {
String useAsUsername =
applicationProperties
.getSecurity()
.getOAUTH2()
.getOauth2()
.getUseAsUsername();
Optional<User> userOpt =
userService.findByUsernameIgnoreCase(

View file

@ -159,7 +159,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
};
for (String pattern : permitAllPatterns) {
if (uri.startsWith(pattern) || uri.endsWith(".svg") || uri.endsWith(".png") || uri.endsWith(".ico")) {
if (uri.startsWith(pattern)
|| uri.endsWith(".svg")
|| uri.endsWith(".png")
|| uri.endsWith(".ico")) {
return true;
}
}

View file

@ -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;
@ -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();
}
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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();

View file

@ -27,9 +27,9 @@ 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.config.PdfMetadataService;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -51,7 +51,7 @@ 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(
@ -79,7 +79,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);

View file

@ -31,9 +31,9 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import stirling.software.SPDF.config.PdfMetadataService;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -258,7 +258,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 +273,7 @@ public class SplitPdfByChaptersController {
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (includeMetadata) {
PdfUtils.setMetadataToPdf(splitDocument, metadata);
PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
}
splitDocument.save(baos);

View file

@ -208,8 +208,8 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/saveUser")
public RedirectView saveUser(
@RequestParam String username,
@RequestParam(name = "password", required = false) String password,
@RequestParam(name = "username", required = true) String username,
@RequestParam(name = "password", required = true) String password,
@RequestParam(name = "role") String role,
@RequestParam(name = "authType") String authType,
@RequestParam(name = "forceChange", required = false, defaultValue = "false")

View file

@ -25,9 +25,9 @@ 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.config.PdfMetadataService;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -46,7 +46,7 @@ public class FlattenController {
MultipartFile file = request.getFileInput();
PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
if (Boolean.TRUE.equals(flattenOnlyForms)) {
@ -80,7 +80,7 @@ public class FlattenController {
logger.error("exception", e);
}
}
PdfUtils.setMetadataToPdf(newDocument, metadata);
PdfMetadataService.setMetadataToPdf(newDocument, metadata);
return WebResponseUtils.pdfDocToWebResponse(
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
}

View file

@ -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);
}
}
}

View file

@ -2,4 +2,6 @@ package stirling.software.SPDF.controller.api.pipeline;
public interface UserServiceInterface {
String getApiKeyForUser(String username);
String getCurrentUsername();
}

View file

@ -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);
}

View file

@ -2,11 +2,7 @@ package stirling.software.SPDF.controller.web;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
@ -18,19 +14,20 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.StartupApplicationListener;
import stirling.software.SPDF.model.ApplicationProperties;
@RestController
@RequestMapping("/api/v1/info")
@Tag(name = "Info", description = "Info APIs")
@Slf4j
public class MetricsController {
@Autowired ApplicationProperties applicationProperties;
@ -46,6 +43,7 @@ public class MetricsController {
this.metricsEnabled = metricsEnabled;
}
@Autowired
public MetricsController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@ -66,11 +64,11 @@ public class MetricsController {
return ResponseEntity.ok(status);
}
@GetMapping("/loads")
@GetMapping("/load")
@Operation(
summary = "GET request count",
description =
"This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
"This endpoint returns the total count of GET requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getPageLoads(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
@ -78,44 +76,33 @@ public class MetricsController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = 0.0;
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "GET".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
System.out.println(
"loads "
+ endpoint.get()
+ " vs "
+ meter.getId().getTag("uri"));
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
} else {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
}
}
}
double count = getRequestCount("GET", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/loads/all")
@GetMapping("/load/unique")
@Operation(
summary = "Unique users count for GET requests",
description =
"This endpoint returns the count of unique users for GET requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getUniquePageLoads(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getUniqueUserCount("GET", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/load/all")
@Operation(
summary = "GET requests count for all endpoints",
description = "This endpoint returns the count of GET requests for each endpoint.")
@ -124,37 +111,191 @@ public class MetricsController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
Map<String, Double> counts = new HashMap<>();
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "GET".equals(method)) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
List<EndpointCount> results = getEndpointCounts("GET");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
public class EndpointCount {
@GetMapping("/load/all/unique")
@Operation(
summary = "Unique users count for GET requests for all endpoints",
description =
"This endpoint returns the count of unique users for GET requests for each endpoint.")
public ResponseEntity<?> getAllUniqueEndpointLoads() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getUniqueUserCounts("GET");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/requests")
@Operation(
summary = "POST request count",
description =
"This endpoint returns the total count of POST requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getRequestCount("POST", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/unique")
@Operation(
summary = "Unique users count for POST requests",
description =
"This endpoint returns the count of unique users for POST requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getUniqueTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getUniqueUserCount("POST", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/all")
@Operation(
summary = "POST requests count for all endpoints",
description = "This endpoint returns the count of POST requests for each endpoint.")
public ResponseEntity<?> getAllPostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getEndpointCounts("POST");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/requests/all/unique")
@Operation(
summary = "Unique users count for POST requests for all endpoints",
description =
"This endpoint returns the count of unique users for POST requests for each endpoint.")
public ResponseEntity<?> getAllUniquePostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getUniqueUserCounts("POST");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private double getRequestCount(String method, Optional<String> endpoint) {
log.info(
"Getting request count for method: {}, endpoint: {}",
method,
endpoint.orElse("all"));
double count =
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get()
.equals(counter.getId().getTag("uri")))
.mapToDouble(Counter::count)
.sum();
log.info("Request count: {}", count);
return count;
}
private List<EndpointCount> getEndpointCounts(String method) {
log.info("Getting endpoint counts for method: {}", method);
Map<String, Double> counts = new HashMap<>();
meterRegistry
.find("http.requests")
.tag("method", method)
.counters()
.forEach(
counter -> {
String uri = counter.getId().getTag("uri");
counts.merge(uri, counter.count(), Double::sum);
});
List<EndpointCount> result =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
log.info("Found {} endpoints with counts", result.size());
return result;
}
private double getUniqueUserCount(String method, Optional<String> endpoint) {
log.info(
"Getting unique user count for method: {}, endpoint: {}",
method,
endpoint.orElse("all"));
Set<String> uniqueUsers = new HashSet<>();
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get().equals(counter.getId().getTag("uri")))
.forEach(
counter -> {
String session = counter.getId().getTag("session");
if (session != null) {
uniqueUsers.add(session);
}
});
log.info("Unique user count: {}", uniqueUsers.size());
return uniqueUsers.size();
}
private List<EndpointCount> getUniqueUserCounts(String method) {
log.info("Getting unique user counts for method: {}", method);
Map<String, Set<String>> uniqueUsers = new HashMap<>();
meterRegistry
.find("http.requests")
.tag("method", method)
.counters()
.forEach(
counter -> {
String uri = counter.getId().getTag("uri");
String session = counter.getId().getTag("session");
if (uri != null && session != null) {
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
}
});
List<EndpointCount> result =
uniqueUsers.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
log.info("Found {} endpoints with unique user counts", result.size());
return result;
}
public static class EndpointCount {
private String endpoint;
private double count;
@ -180,86 +321,6 @@ public class MetricsController {
}
}
@GetMapping("/requests")
@Operation(
summary = "POST request count",
description =
"This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
public ResponseEntity<?> getTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = 0.0;
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "POST".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
} else {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
}
}
}
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/all")
@Operation(
summary = "POST requests count for all endpoints",
description = "This endpoint returns the count of POST requests for each endpoint.")
public ResponseEntity<?> getAllPostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
Map<String, Double> counts = new HashMap<>();
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "POST".equals(method)) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/uptime")
public ResponseEntity<?> getUptime() {
if (!metricsEnabled) {

View file

@ -12,6 +12,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import lombok.Data;
import lombok.ToString;
import stirling.software.SPDF.config.YamlPropertySourceFactory;
import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider;
@ -21,225 +23,56 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Configuration
@ConfigurationProperties(prefix = "")
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
@Data
public class ApplicationProperties {
private Security security;
private System system;
private Ui ui;
private Endpoints endpoints;
private Metrics metrics;
private AutomaticallyGenerated automaticallyGenerated;
private AutoPipeline autoPipeline;
private Legal legal = new Legal();
private Security security = new Security();
private System system = new System();
private Ui ui = new Ui();
private Endpoints endpoints = new Endpoints();
private Metrics metrics = new Metrics();
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
private AutoPipeline autoPipeline = new AutoPipeline();
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
public AutoPipeline getAutoPipeline() {
return autoPipeline != null ? autoPipeline : new AutoPipeline();
}
public void setAutoPipeline(AutoPipeline autoPipeline) {
this.autoPipeline = autoPipeline;
}
public Security getSecurity() {
return security != null ? security : new Security();
}
public void setSecurity(Security security) {
this.security = security;
}
public System getSystem() {
return system != null ? system : new System();
}
public void setSystem(System system) {
this.system = system;
}
public Ui getUi() {
return ui != null ? ui : new Ui();
}
public void setUi(Ui ui) {
this.ui = ui;
}
public Endpoints getEndpoints() {
return endpoints != null ? endpoints : new Endpoints();
}
public void setEndpoints(Endpoints endpoints) {
this.endpoints = endpoints;
}
public Metrics getMetrics() {
return metrics != null ? metrics : new Metrics();
}
public void setMetrics(Metrics metrics) {
this.metrics = metrics;
}
public AutomaticallyGenerated getAutomaticallyGenerated() {
return automaticallyGenerated != null
? automaticallyGenerated
: new AutomaticallyGenerated();
}
public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) {
this.automaticallyGenerated = automaticallyGenerated;
}
@Override
public String toString() {
return "ApplicationProperties [security="
+ security
+ ", system="
+ system
+ ", ui="
+ ui
+ ", endpoints="
+ endpoints
+ ", metrics="
+ metrics
+ ", automaticallyGenerated="
+ automaticallyGenerated
+ ", autoPipeline="
+ autoPipeline
+ "]";
}
@Data
public static class AutoPipeline {
private String outputFolder;
public String getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(String outputFolder) {
this.outputFolder = outputFolder;
}
@Override
public String toString() {
return "AutoPipeline [outputFolder=" + outputFolder + "]";
}
@Data
public static class Legal {
private String termsAndConditions;
private String privacyPolicy;
private String accessibilityStatement;
private String cookiePolicy;
private String impressum;
}
@Data
public static class Security {
private Boolean enableLogin;
private Boolean csrfDisabled;
private InitialLogin initialLogin;
private OAUTH2 oauth2;
private InitialLogin initialLogin = new InitialLogin();
private OAUTH2 oauth2 = new OAUTH2();
private int loginAttemptCount;
private long loginResetTimeMinutes;
private String loginMethod = "all";
public String getLoginMethod() {
return loginMethod;
}
public void setLoginMethod(String loginMethod) {
this.loginMethod = loginMethod;
}
public int getLoginAttemptCount() {
return loginAttemptCount;
}
public void setLoginAttemptCount(int loginAttemptCount) {
this.loginAttemptCount = loginAttemptCount;
}
public long getLoginResetTimeMinutes() {
return loginResetTimeMinutes;
}
public void setLoginResetTimeMinutes(long loginResetTimeMinutes) {
this.loginResetTimeMinutes = loginResetTimeMinutes;
}
public InitialLogin getInitialLogin() {
return initialLogin != null ? initialLogin : new InitialLogin();
}
public void setInitialLogin(InitialLogin initialLogin) {
this.initialLogin = initialLogin;
}
public OAUTH2 getOAUTH2() {
return oauth2 != null ? oauth2 : new OAUTH2();
}
public void setOAUTH2(OAUTH2 oauth2) {
this.oauth2 = oauth2;
}
public Boolean getEnableLogin() {
return enableLogin;
}
public void setEnableLogin(Boolean enableLogin) {
this.enableLogin = enableLogin;
}
public Boolean getCsrfDisabled() {
return csrfDisabled;
}
public void setCsrfDisabled(Boolean csrfDisabled) {
this.csrfDisabled = csrfDisabled;
}
@Override
public String toString() {
return "Security [enableLogin="
+ enableLogin
+ ", oauth2="
+ oauth2
+ ", initialLogin="
+ initialLogin
+ ", csrfDisabled="
+ csrfDisabled
+ ", loginMethod="
+ loginMethod
+ "]";
}
@Data
public static class InitialLogin {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "InitialLogin [username="
+ username
+ ", password="
+ (password != null && !password.isEmpty() ? "MASKED" : "NULL")
+ "]";
}
@ToString.Exclude private String password;
}
@Data
public static class OAUTH2 {
private Boolean enabled = false;
private String issuer;
private String clientId;
private String clientSecret;
@ToString.Exclude private String clientSecret;
private Boolean autoCreateUser = false;
private Boolean blockRegistration = false;
private String useAsUsername;
@ -247,74 +80,6 @@ public class ApplicationProperties {
private String provider;
private Client client = new Client();
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public Boolean getAutoCreateUser() {
return autoCreateUser;
}
public void setAutoCreateUser(Boolean autoCreateUser) {
this.autoCreateUser = autoCreateUser;
}
public Boolean getBlockRegistration() {
return blockRegistration;
}
public void setBlockRegistration(Boolean blockRegistration) {
this.blockRegistration = blockRegistration;
}
public String getUseAsUsername() {
return useAsUsername;
}
public void setUseAsUsername(String useAsUsername) {
this.useAsUsername = useAsUsername;
}
public String getProvider() {
return provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
public Collection<String> getScopes() {
return scopes;
}
public void setScopes(String scopes) {
List<String> scopesList =
Arrays.stream(scopes.split(","))
@ -323,26 +88,12 @@ public class ApplicationProperties {
this.scopes.addAll(scopesList);
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
protected boolean isValid(String value, String name) {
if (value != null && !value.trim().isEmpty()) {
return true;
}
return false;
return value != null && !value.trim().isEmpty();
}
protected boolean isValid(Collection<String> value, String name) {
if (value != null && !value.isEmpty()) {
return true;
}
return false;
return value != null && !value.isEmpty();
}
public boolean isSettingsValid() {
@ -353,31 +104,7 @@ public class ApplicationProperties {
&& isValid(this.getUseAsUsername(), "useAsUsername");
}
@Override
public String toString() {
return "OAUTH2 [enabled="
+ enabled
+ ", issuer="
+ issuer
+ ", clientId="
+ clientId
+ ", clientSecret="
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
+ ", autoCreateUser="
+ autoCreateUser
+ ", blockRegistration="
+ blockRegistration
+ ", useAsUsername="
+ useAsUsername
+ ", provider="
+ provider
+ ", client="
+ client
+ ", scopes="
+ scopes
+ "]";
}
@Data
public static class Client {
private GoogleProvider google = new GoogleProvider();
private GithubProvider github = new GithubProvider();
@ -392,50 +119,15 @@ public class ApplicationProperties {
case "keycloak":
return getKeycloak();
default:
break;
}
throw new UnsupportedProviderException(
"Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
}
public GoogleProvider getGoogle() {
return google;
}
public void setGoogle(GoogleProvider google) {
this.google = google;
}
public GithubProvider getGithub() {
return github;
}
public void setGithub(GithubProvider github) {
this.github = github;
}
public KeycloakProvider getKeycloak() {
return keycloak;
}
public void setKeycloak(KeycloakProvider keycloak) {
this.keycloak = keycloak;
}
@Override
public String toString() {
return "Client [google="
+ google
+ ", github="
+ github
+ ", keycloak="
+ keycloak
+ "]";
}
}
}
}
@Data
public static class System {
private String defaultLocale;
private Boolean googlevisibility;
@ -443,184 +135,67 @@ public class ApplicationProperties {
private Boolean showUpdateOnlyAdmin;
private boolean customHTMLFiles;
private String tessdataDir;
public String getTessdataDir() {
return tessdataDir;
}
public void setTessdataDir(String tessdataDir) {
this.tessdataDir = tessdataDir;
}
public boolean isCustomHTMLFiles() {
return customHTMLFiles;
}
public void setCustomHTMLFiles(boolean customHTMLFiles) {
this.customHTMLFiles = customHTMLFiles;
}
public boolean getShowUpdateOnlyAdmin() {
return showUpdateOnlyAdmin;
}
public void setShowUpdateOnlyAdmin(boolean showUpdateOnlyAdmin) {
this.showUpdateOnlyAdmin = showUpdateOnlyAdmin;
}
public boolean getShowUpdate() {
return showUpdate;
}
public void setShowUpdate(boolean showUpdate) {
this.showUpdate = showUpdate;
}
private Boolean enableAlphaFunctionality;
public Boolean getEnableAlphaFunctionality() {
return enableAlphaFunctionality;
}
public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) {
this.enableAlphaFunctionality = enableAlphaFunctionality;
}
public String getDefaultLocale() {
return defaultLocale;
}
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public Boolean getGooglevisibility() {
return googlevisibility;
}
public void setGooglevisibility(Boolean googlevisibility) {
this.googlevisibility = googlevisibility;
}
@Override
public String toString() {
return "System [defaultLocale="
+ defaultLocale
+ ", googlevisibility="
+ googlevisibility
+ ", enableAlphaFunctionality="
+ enableAlphaFunctionality
+ ", showUpdate="
+ showUpdate
+ ", showUpdateOnlyAdmin="
+ showUpdateOnlyAdmin
+ "]";
}
}
@Data
public static class Ui {
private String appName;
private String homeDescription;
private String appNameNavbar;
public String getAppName() {
if (appName != null && appName.trim().length() == 0) return null;
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
return appName != null && appName.trim().length() > 0 ? appName : null;
}
public String getHomeDescription() {
if (homeDescription != null && homeDescription.trim().length() == 0) return null;
return homeDescription;
}
public void setHomeDescription(String homeDescription) {
this.homeDescription = homeDescription;
return homeDescription != null && homeDescription.trim().length() > 0
? homeDescription
: null;
}
public String getAppNameNavbar() {
if (appNameNavbar != null && appNameNavbar.trim().length() == 0) return null;
return appNameNavbar;
}
public void setAppNameNavbar(String appNameNavbar) {
this.appNameNavbar = appNameNavbar;
}
@Override
public String toString() {
return "UserInterface [appName="
+ appName
+ ", homeDescription="
+ homeDescription
+ ", appNameNavbar="
+ appNameNavbar
+ "]";
return appNameNavbar != null && appNameNavbar.trim().length() > 0
? appNameNavbar
: null;
}
}
@Data
public static class Endpoints {
private List<String> toRemove;
private List<String> groupsToRemove;
public List<String> getToRemove() {
return toRemove;
}
public void setToRemove(List<String> toRemove) {
this.toRemove = toRemove;
}
public List<String> getGroupsToRemove() {
return groupsToRemove;
}
public void setGroupsToRemove(List<String> groupsToRemove) {
this.groupsToRemove = groupsToRemove;
}
@Override
public String toString() {
return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]";
}
}
@Data
public static class Metrics {
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "Metrics [enabled=" + enabled + "]";
}
}
@Data
public static class AutomaticallyGenerated {
private String key;
public String getKey() {
return key;
@ToString.Exclude private String key;
}
public void setKey(String key) {
this.key = key;
@Data
public static class EnterpriseEdition {
@ToString.Exclude private String key;
private CustomMetadata customMetadata = new CustomMetadata();
@Data
public static class CustomMetadata {
private boolean autoUpdateMetadata;
private String author;
private String creator;
private String producer;
public String getCreator() {
return creator == null || creator.trim().isEmpty() ? "Stirling-PDF" : creator;
}
@Override
public String toString() {
return "AutomaticallyGenerated [key="
+ (key != null && !key.isEmpty() ? "MASKED" : "NULL")
+ "]";
public String getProducer() {
return producer == null || producer.trim().isEmpty() ? "Stirling-PDF" : producer;
}
}
}
}

View file

@ -16,7 +16,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
@Query("FROM User u LEFT JOIN FETCH u.settings where upper(u.username) = upper(:username)")
Optional<User> findByUsernameIgnoreCaseWithSettings(@Param("username") String username);
Optional<User> findByUsername(String username);
Optional<User> findByApiKey(String apiKey);

View file

@ -6,7 +6,6 @@ import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
@ -37,8 +36,6 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import stirling.software.SPDF.model.PdfMetadata;
public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
@ -506,30 +503,6 @@ public class PdfUtils {
return baos.toByteArray();
}
public static PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
return PdfMetadata.builder()
.author(pdf.getDocumentInformation().getAuthor())
.producer(pdf.getDocumentInformation().getProducer())
.title(pdf.getDocumentInformation().getTitle())
.creator(pdf.getDocumentInformation().getCreator())
.subject(pdf.getDocumentInformation().getSubject())
.keywords(pdf.getDocumentInformation().getKeywords())
.creationDate(pdf.getDocumentInformation().getCreationDate())
.modificationDate(pdf.getDocumentInformation().getModificationDate())
.build();
}
public static void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
pdf.getDocumentInformation().setAuthor(pdfMetadata.getAuthor());
pdf.getDocumentInformation().setProducer(pdfMetadata.getProducer());
pdf.getDocumentInformation().setTitle(pdfMetadata.getTitle());
pdf.getDocumentInformation().setCreator(pdfMetadata.getCreator());
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
pdf.getDocumentInformation().setCreationDate(pdfMetadata.getCreationDate());
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
}
/** Key for storing the dimensions of a rendered image in a map. */
private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {}

View file

@ -12,6 +12,7 @@ public class RequestUriUtils {
return requestURI.startsWith(contextPath + "/css/")
|| requestURI.startsWith(contextPath + "/fonts/")
|| requestURI.startsWith(contextPath + "/js/")
|| requestURI.endsWith(contextPath + "robots.txt")
|| requestURI.startsWith(contextPath + "/images/")
|| requestURI.startsWith(contextPath + "/public/")
|| requestURI.startsWith(contextPath + "/pdfjs/")
@ -22,4 +23,26 @@ public class RequestUriUtils {
|| requestURI.endsWith(".webmanifest")
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
}
public static boolean isTrackableResource(String requestURI) {
return isTrackableResource("", requestURI);
}
public static boolean isTrackableResource(String contextPath, String requestURI) {
return !(requestURI.startsWith("/js")
|| requestURI.startsWith("/v1/api-docs")
|| requestURI.endsWith("robots.txt")
|| requestURI.startsWith("/images")
|| requestURI.endsWith(".png")
|| requestURI.endsWith(".ico")
|| requestURI.endsWith(".css")
|| requestURI.endsWith(".map")
|| requestURI.endsWith(".svg")
|| requestURI.endsWith(".js")
|| requestURI.contains("swagger")
|| requestURI.startsWith("/api/v1/info")
|| requestURI.startsWith("/site.webmanifest")
|| requestURI.startsWith("/fonts")
|| requestURI.startsWith("/pdfjs"));
}
}

View file

@ -77,7 +77,11 @@ color=لون
sponsor=راعٍ
info=معلومات
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Цвят
sponsor=Спонсор
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Barva
sponsor=Sponzor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Farve
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Farbe
sponsor=Sponsor
info=Informationen
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Χρώμα
sponsor=οστηρικτής
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Patrocinador
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Couleur
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Dath
sponsor=Urraitheoir
info=Eolas
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Boja
sponsor=Sponzor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Colore
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=色
sponsor=スポンサー
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=색상
sponsor=스폰서
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Kleur
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Farge
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=kolor
sponsor=sponsor
info=informacje
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Cor
sponsor=Patrocine
info=Informações
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Culoare
sponsor=Sponsor
info=Informații
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Цвет
sponsor=Спонсор
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Farba
sponsor=Sponzorovať
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Färg
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=สี
sponsor=ผู้สนับสนุน
info=ข้อมูล
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Renk
sponsor=Bağış
info=Bilgi
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Колір
sponsor=Спонсор
info=Інформація
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=Màu sắc
sponsor=Nhà tài trợ
info=Thông tin
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=颜色
sponsor=赞助
info=信息
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -77,7 +77,11 @@ color=顏色
sponsor=贊助
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #

View file

@ -48,6 +48,22 @@ security:
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
# Enterprise edition settings unused for now please ignore!
EnterpriseEdition:
key: 00000000-0000-0000-0000-000000000000
CustomMetadata:
autoUpdateMetadata: true # set to 'true' to automatically update metadata with below values
author: username # Supports text such as 'John Doe' or types such as username
creator: Stirling-PDF # Supports text such as 'Company-PDF'
producer: Stirling-PDF # Supports text such as 'Company-PDF'
legal:
termsAndConditions: '' # URL to the terms and conditions of your application (e.g. https://example.com/terms) Empty string to disable or filename to load from local file in static folder
privacyPolicy: '' # URL to the privacy policy of your application (e.g. https://example.com/privacy) Empty string to disable or filename to load from local file in static folder
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility) Empty string to disable or filename to load from local file in static folder
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie) Empty string to disable or filename to load from local file in static folder
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum) Empty string to disable or filename to load from local file in static folder
system:
defaultLocale: en-US # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow

View file

@ -24,7 +24,7 @@
<!-- jQuery -->
<script th:src="@{'/js/thirdParty/jquery.min.js'}"></script>
<script th:src="@{'/js/thirdParty/jquery.validate.min.js'}"></script>
<script th:src="@{'/js/thirdParty/jszip.min.js'}"></script>
<script th:src="@{'/js/thirdParty/jszip.min.js'}" th:if="${currentPage != 'home'}"></script>
<!-- Bootstrap -->
<script th:src="@{'/js/thirdParty/popper.min.js'}"></script>
@ -35,10 +35,10 @@
<link rel="stylesheet" th:href="@{'/css/bootstrap-icons.min.css'}">
<!-- PDF.js -->
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}" th:if="${currentPage != 'home'}"></script>
<!-- PDF-Lib -->
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}" th:if="${currentPage != 'home'}"></script>
<!-- Custom -->
<link rel="stylesheet" th:href="@{'/css/general.css'}">
@ -58,17 +58,21 @@
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
<link rel="stylesheet" th:href="@{'/css/rotate-pdf.css'}" th:if="${currentPage == 'rotate-pdf'}">
<link rel="stylesheet" th:href="@{'/css/stamp.css'}" th:if="${currentPage == 'stamp'}">
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}">
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
<link rel="stylesheet" th:href="@{'/css/footer.css'}">
<link rel="preload" href="/fonts/google-symbol.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<script th:src="@{'/js/thirdParty/fontfaceobserver.standalone.js'}"></script>
<!-- Google MD Icons -->
<link rel="stylesheet" th:href="@{'/css/theme/font.css'}">
<!-- Help Modal -->
<link rel="stylesheet" th:href="@{'/css/errorBanner.css'}">
<link rel="stylesheet" th:href="@{'/css/errorBanner.css'}" th:if="${currentPage != 'home'}">
<script th:src="@{'/js/cacheFormInputs.js'}"></script>
<script th:src="@{'/js/cacheFormInputs.js'}" th:if="${currentPage != 'home'}"></script>
<script th:src="@{'/js/tab-container.js'}"></script>
<script th:src="@{'/js/darkmode.js'}"></script>
</th:block>

View file

@ -5,6 +5,11 @@
<ul class="list-unstyled d-flex">
<li><a class="footer-link px-2" id="licenses" target="_blank" th:href="@{'/licenses'}" th:text="#{licenses.nav}">Licenses</a></li>
<li><a class="footer-link px-2" id="survey" target="_blank" href="https://stirlingpdf.info/s/clwzgtfw7000gltkmwz1n212m" th:text="#{survey.nav}">Survey</a></li>
<li th:if="${@privacyPolicy != ''}"><a class="footer-link px-2" target="_blank" th:href="${@privacyPolicy}" th:text="#{legal.privacy}">privacyPolicy</a></li>
<li th:if="${@termsAndConditions != ''}"><a class="footer-link px-2" target="_blank" th:href="${@termsAndConditions}" th:text="#{legal.terms}">termsAndConditions</a></li>
<li th:if="${@accessibilityStatement != ''}"><a class="footer-link px-2" target="_blank" th:href="${@accessibilityStatement}" th:text="#{legal.accessibility}">accessibilityStatement</a></li>
<li th:if="${@cookiePolicy != ''}"><a class="footer-link px-2" target="_blank" th:href="${@cookiePolicy}" th:text="#{legal.cookie}">cookiePolicy</a></li>
<li th:if="${@impressum != ''}"><a class="footer-link px-2" target="_blank" th:href="${@impressum}" th:text="#{legal.impressum}">impressum</a></li>
</ul>
</div>

View file

@ -94,10 +94,9 @@
<div class="mb-3">
<label for="fontType" th:text="#{addPageNumbers.fontName}"></label>
<select class="form-control" id="fontType" name="fontType">
<option value="Times Roman">Times</option>
<option value="Times">Times Roman</option>
<option value="Helvetica">Helvetica</option>
<option value="Courier New">Courier</option>
<option value="Courier">Courier New</option>
</select>
</div>
<div class="mb-3">

View file

@ -49,12 +49,5 @@ public class PdfUtilsTest {
assertTrue(PdfUtils.hasImagesOnPage(page));
}
@Test
void testExtractMetadataFromPdf() throws IOException {
PDDocument document = Mockito.mock(PDDocument.class);
Mockito.when(document.getDocumentInformation()).thenReturn(Mockito.mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class));
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
assertNotNull(metadata);
}
}