From 7c503eea6d0f002923c291c346af0ee6f9525cf0 Mon Sep 17 00:00:00 2001 From: Ludy87 Date: Tue, 2 Sep 2025 18:02:53 +0200 Subject: [PATCH] Add internal legal policy pages and config support --- .../common/model/ApplicationProperties.java | 8 ++ .../software/SPDF/config/OpenApiConfig.java | 100 ++++++++++++------ .../controller/web/HomeWebController.java | 48 ++++++++- .../src/main/resources/settings.yml.template | 31 +++++- .../templates/accessibilityStatement.html | 58 ++++++++++ .../resources/templates/cookiePolicy.html | 58 ++++++++++ .../resources/templates/fragments/footer.html | 10 +- .../main/resources/templates/impressum.html | 75 +++++++++++++ .../resources/templates/privacyPolicy.html | 59 +++++++++++ .../templates/termsAndConditions.html | 58 ++++++++++ .../security/config/AccountWebController.java | 5 + .../configuration/SecurityConfiguration.java | 31 ++++++ .../filter/UserAuthenticationFilter.java | 5 + testing/webpage_urls.txt | 90 ++++++++-------- testing/webpage_urls_full.txt | 7 +- 15 files changed, 554 insertions(+), 89 deletions(-) create mode 100644 app/core/src/main/resources/templates/accessibilityStatement.html create mode 100644 app/core/src/main/resources/templates/cookiePolicy.html create mode 100644 app/core/src/main/resources/templates/impressum.html create mode 100644 app/core/src/main/resources/templates/privacyPolicy.html create mode 100644 app/core/src/main/resources/templates/termsAndConditions.html diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 5845c6d16..4c1d40a59 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -106,6 +106,14 @@ public class ApplicationProperties { private String accessibilityStatement; private String cookiePolicy; private String impressum; + private ApiContact apiContact = new ApiContact(); + } + + @Data + public static class ApiContact { + private String company; + private String website; + private String email; } @Data diff --git a/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index 78d2a3d2b..8bf5e7a1a 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -1,5 +1,8 @@ package stirling.software.SPDF.config; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,57 +14,90 @@ import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; -import lombok.RequiredArgsConstructor; - import stirling.software.common.model.ApplicationProperties; @Configuration -@RequiredArgsConstructor public class OpenApiConfig { private final ApplicationProperties applicationProperties; + private final boolean runningProOrHigher; - private static final String DEFAULT_TITLE = "Stirling PDF API"; + public OpenApiConfig( + ApplicationProperties applicationProperties, + @Qualifier("runningProOrHigher") boolean runningProOrHigher) { + this.applicationProperties = applicationProperties; + this.runningProOrHigher = runningProOrHigher; + } + + private static final String DEFAULT_COMPANY = "Stirling Software"; private static final String DEFAULT_DESCRIPTION = "API documentation for all Server-Side processing.\n" + "Please note some functionality might be UI only and missing from here."; + private static final String DEFAULT_EMAIL = "contact@stirlingpdf.com"; + private static final String DEFAULT_TERMS_OF_SERVICE = "https://www.stirlingpdf.com/terms"; + private static final String DEFAULT_TITLE = "Stirling PDF API"; + private static final String DEFAULT_WEBSITE = "https://www.stirlingpdf.com"; + private static final String DEFAULT_VERSION = "1.0.0"; @Bean public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - if (version == null) { - // default version if all else fails - version = "1.0.0"; + String version = + Optional.ofNullable(applicationProperties.getAutomaticallyGenerated()) + .map(ApplicationProperties.AutomaticallyGenerated::getAppVersion) + .filter(v -> !v.isBlank()) + .orElseGet( + () -> { + String v = getClass().getPackage().getImplementationVersion(); + return v != null ? v : DEFAULT_VERSION; + }); + String title = DEFAULT_TITLE; + String description = DEFAULT_DESCRIPTION; + String termsOfService = DEFAULT_TERMS_OF_SERVICE; + String company = DEFAULT_COMPANY; + String email = DEFAULT_EMAIL; + String website = DEFAULT_WEBSITE; + + License license = + new License() + .name("MIT License") + .url( + "https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE") + .identifier("MIT"); + + if (runningProOrHigher) { + ApplicationProperties.Ui ui = applicationProperties.getUi(); + ApplicationProperties.Legal legal = applicationProperties.getLegal(); + + ApplicationProperties.ApiContact apiContact = legal.getApiContact(); + title = Optional.ofNullable(ui.getAppName()).orElse(title); + description = Optional.ofNullable(ui.getHomeDescription()).orElse(description); + termsOfService = + Optional.ofNullable(legal.getTermsAndConditions()).orElse(termsOfService); + company = Optional.ofNullable(apiContact.getCompany()).orElse(company); + website = Optional.ofNullable(apiContact.getWebsite()).orElse(website); + email = Optional.ofNullable(apiContact.getEmail()).orElse(email); } + Contact contact = new Contact().name(company).url(website).email(email); + Info info = new Info() - .title(DEFAULT_TITLE) + .title(title) .version(version) - .license( - new License() - .name("MIT") - .url( - "https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE") - .identifier("MIT")) - .termsOfService("https://www.stirlingpdf.com/terms") - .contact( - new Contact() - .name("Stirling Software") - .url("https://www.stirlingpdf.com") - .email("contact@stirlingpdf.com")) - .description(DEFAULT_DESCRIPTION); + .license(license) + .termsOfService(termsOfService) + .contact(contact) + .description(description); if (!applicationProperties.getSecurity().getEnableLogin()) { return new OpenAPI().components(new Components()).info(info); - } else { - SecurityScheme apiKeyScheme = - new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .in(SecurityScheme.In.HEADER) - .name("X-API-KEY"); - return new OpenAPI() - .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) - .info(info) - .addSecurityItem(new SecurityRequirement().addList("apiKey")); } + SecurityScheme apiKeyScheme = + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("X-API-KEY"); + return new OpenAPI() + .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) + .info(info) + .addSecurityItem(new SecurityRequirement().addList("apiKey")); } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java index 2b36f95af..03daa184e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java @@ -8,11 +8,13 @@ import java.util.Map; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.server.ResponseStatusException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -44,8 +46,7 @@ public class HomeWebController { public String licensesForm(Model model) { model.addAttribute("currentPage", "licenses"); Resource resource = new ClassPathResource("static/3rdPartyLicenses.json"); - try { - InputStream is = resource.getInputStream(); + try (InputStream is = resource.getInputStream()) { String json = new String(is.readAllBytes(), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map> data = mapper.readValue(json, new TypeReference<>() {}); @@ -56,6 +57,42 @@ public class HomeWebController { return "licenses"; } + @GetMapping("/impressum") + @Hidden + public String impressum() { + String impressum = applicationProperties.getLegal().getImpressum(); + return internalPage(impressum, "impressum"); + } + + @GetMapping("/cookiePolicy") + @Hidden + public String cookiePolicy() { + String cookiePolicy = applicationProperties.getLegal().getCookiePolicy(); + return internalPage(cookiePolicy, "cookiePolicy"); + } + + @GetMapping("/privacyPolicy") + @Hidden + public String privacyPolicy() { + String privacyPolicy = applicationProperties.getLegal().getPrivacyPolicy(); + return internalPage(privacyPolicy, "privacyPolicy"); + } + + @GetMapping("/termsAndConditions") + @Hidden + public String termsAndConditions() { + String termsAndConditions = applicationProperties.getLegal().getTermsAndConditions(); + return internalPage(termsAndConditions, "termsAndConditions"); + } + + @GetMapping("/accessibilityStatement") + @Hidden + public String accessibilityStatement() { + String accessibilityStatement = + applicationProperties.getLegal().getAccessibilityStatement(); + return internalPage(accessibilityStatement, "accessibilityStatement"); + } + @GetMapping("/releases") public String getReleaseNotes(Model model) { return "releases"; @@ -91,4 +128,11 @@ public class HomeWebController { return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /"; } } + + private String internalPage(String configured, String view) { + if (configured != null && (("/" + view).equals(configured) || view.equals(configured))) { + return view; + } + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } } diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index bbbac5fcd..114a9822d 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -95,12 +95,33 @@ mail: password: '' # SMTP server password from: '' # sender email address +############################################################################################################# +# Declaration of settings for URLs # +# # +# To disable, set termsAndConditions, privacyPolicy, accessibilityStatement, cookiePolicy, impressum to '/' # +# # +# If you want to use an internal file, place the file in the 'customFiles/templates/' folder # +# For termsAndConditions, the file must be named termsAndConditions.html # +# For privacyPolicy, the file must be named privacyPolicy.html # +# For accessibilityStatement, the file must be named accessibilityStatement.html # +# For cookiePolicy, the file must be named cookiePolicy.html # +# For impressum, the file must be named impressum.html # +# To use an internal URL, it must be entered without `.html` # +# # +# For example: termsAndConditions: /termsAndConditions # +# # +# You can also use external URLs, for example: https://example.com/terms # +############################################################################################################# legal: - termsAndConditions: https://www.stirlingpdf.com/terms # 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: https://www.stirlingpdf.com/privacy-policy # 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 + termsAndConditions: / # URL to the terms and conditions of your application + privacyPolicy: / # URL to the privacy policy of your application + accessibilityStatement: / # URL to the accessibility statement of your application + cookiePolicy: / # URL to the cookie policy of your application + impressum: /impressum # URL to the impressum of your application + apiContact: # ONLY pro/enterprise features + company: Stirling Software + email: contact@stirlingpdf.com + website: https://www.stirlingpdf.com system: defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc) diff --git a/app/core/src/main/resources/templates/accessibilityStatement.html b/app/core/src/main/resources/templates/accessibilityStatement.html new file mode 100644 index 000000000..e88987c92 --- /dev/null +++ b/app/core/src/main/resources/templates/accessibilityStatement.html @@ -0,0 +1,58 @@ + + + + + + + + +
+
+ +

+
+
+
+
+ accessibility + Accessibility +
+ + +
+ +
+ + +
+

Accessibility Statement

+

This is the accessibility statement for our application.

+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/app/core/src/main/resources/templates/cookiePolicy.html b/app/core/src/main/resources/templates/cookiePolicy.html new file mode 100644 index 000000000..df89c91ce --- /dev/null +++ b/app/core/src/main/resources/templates/cookiePolicy.html @@ -0,0 +1,58 @@ + + + + + + + + +
+
+ +

+
+
+
+
+ cookie + Cookie Policy +
+ + +
+ +
+ + +
+

Cookie Policy

+

This application uses cookies to enhance your browsing experience. By continuing to use the site, you agree + to the use of cookies.

+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/footer.html b/app/core/src/main/resources/templates/fragments/footer.html index 89c3c78b1..34501c0db 100644 --- a/app/core/src/main/resources/templates/fragments/footer.html +++ b/app/core/src/main/resources/templates/fragments/footer.html @@ -8,11 +8,11 @@
  • Licenses
  • Releases
  • Survey
  • -
  • privacyPolicy
  • -
  • termsAndConditions
  • -
  • accessibilityStatement
  • -
  • cookiePolicy
  • -
  • impressum
  • +
  • privacyPolicy
  • +
  • termsAndConditions
  • +
  • accessibilityStatement
  • +
  • cookiePolicy
  • +
  • impressum
  • Cookie Preferences
  • diff --git a/app/core/src/main/resources/templates/impressum.html b/app/core/src/main/resources/templates/impressum.html new file mode 100644 index 000000000..e4a432648 --- /dev/null +++ b/app/core/src/main/resources/templates/impressum.html @@ -0,0 +1,75 @@ + + + + + + + + + +
    +
    + +

    +
    +
    +
    +
    + info + Impressum / Legal Notice +
    + + +
    + +
    + + +
    +

    Company Information

    +

    Company Name
    Street Address
    City, Postal Code
    Country

    +
    +
    +

    Contact

    +

    Telephone: +00 000 0000
    Email: info@example.com

    +
    +
    +

    Representatives

    +

    Authorized to represent: Jane Doe, John Doe

    +
    +
    +

    Registration & VAT

    +

    Commercial Register: Local Court of Exampletown, HRB 00000
    VAT ID: XX000000000

    +
    +
    +

    Disclaimer

    +

    This page provides general company and contact information for international use. Legal requirements vary by + jurisdiction; please ensure compliance with local laws.

    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/app/core/src/main/resources/templates/privacyPolicy.html b/app/core/src/main/resources/templates/privacyPolicy.html new file mode 100644 index 000000000..a0daaa59c --- /dev/null +++ b/app/core/src/main/resources/templates/privacyPolicy.html @@ -0,0 +1,59 @@ + + + + + + + + +
    +
    + +

    +
    +
    +
    +
    + privacy_tip + Privacy Policy +
    + + +
    + +
    + + +
    +

    Privacy Policy

    +

    This Privacy Policy explains how we handle your data. No personal information is stored beyond the scope of + providing our services.

    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/app/core/src/main/resources/templates/termsAndConditions.html b/app/core/src/main/resources/templates/termsAndConditions.html new file mode 100644 index 000000000..ecbe40466 --- /dev/null +++ b/app/core/src/main/resources/templates/termsAndConditions.html @@ -0,0 +1,58 @@ + + + + + + + + +
    +
    + +

    +
    +
    +
    +
    + gavel + Terms and Conditions +
    + + +
    + +
    + + +
    +

    Terms and Conditions

    +

    This Terms and Conditions document outlines the rules and regulations for using our services.

    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index a61a7b0fa..0dabf55ed 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -81,6 +81,11 @@ public class AccountWebController { return "redirect:/"; } + boolean enableLogin = applicationProperties.getSecurity().getEnableLogin(); + if (!enableLogin) { + return "redirect:/"; + } + Map providerList = new HashMap<>(); Security securityProps = applicationProperties.getSecurity(); OAUTH2 oauth = securityProps.getOauth2(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index aceb3b712..8baa2346e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -223,6 +223,22 @@ public class SecurityConfiguration { .rememberMeCookieName( // Cookie name "remember-me") .alwaysRemember(false)); + String impressumUrl = appConfig.impressum(); + boolean internalImpressum = impressumUrl != null && impressumUrl.equals("/impressum"); + String accessibilityStatementUrl = appConfig.accessibilityStatement(); + boolean internalAccessibilityStatement = + accessibilityStatementUrl != null + && accessibilityStatementUrl.equals("/accessibilityStatement"); + String cookiePolicyUrl = appConfig.cookiePolicy(); + boolean internalCookiePolicy = + cookiePolicyUrl != null && cookiePolicyUrl.equals("/cookiePolicy"); + String termsAndConditionsUrl = appConfig.termsAndConditions(); + boolean internalTermsAndConditions = + termsAndConditionsUrl != null + && termsAndConditionsUrl.equals("/termsAndConditions"); + String privacyPolicyUrl = appConfig.privacyPolicy(); + boolean internalPrivacyPolicy = + privacyPolicyUrl != null && privacyPolicyUrl.equals("/privacyPolicy"); http.authorizeHttpRequests( authz -> authz.requestMatchers( @@ -250,6 +266,21 @@ public class SecurityConfiguration { || trimmedUri.startsWith("/pdfjs/") || trimmedUri.startsWith("/pdfjs-legacy/") || trimmedUri.startsWith("/favicon") + || (internalAccessibilityStatement + && trimmedUri.startsWith( + "/accessibilityStatement")) + || (internalImpressum + && trimmedUri.startsWith( + "/impressum")) + || (internalCookiePolicy + && trimmedUri.startsWith( + "/cookiePolicy")) + || (internalTermsAndConditions + && trimmedUri.startsWith( + "/termsAndConditions")) + || (internalPrivacyPolicy + && trimmedUri.startsWith( + "/privacyPolicy")) || trimmedUri.startsWith( "/api/v1/info/status") || trimmedUri.startsWith("/v1/api-docs") diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index bec6f1d04..b44852944 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -230,6 +230,11 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { contextPath + "/error", contextPath + "/images/", contextPath + "/public/", + contextPath + "/impressum", + contextPath + "/privacyPolicy", + contextPath + "/accessibilityStatement", + contextPath + "/cookiePolicy", + contextPath + "/termsAndConditions", contextPath + "/css/", contextPath + "/fonts/", contextPath + "/js/", diff --git a/testing/webpage_urls.txt b/testing/webpage_urls.txt index c6c713dd0..2991434bb 100644 --- a/testing/webpage_urls.txt +++ b/testing/webpage_urls.txt @@ -1,54 +1,56 @@ - / -/multi-tool -/merge-pdfs -/split-pdfs -/rotate-pdf -/remove-pages -/pdf-organizer -/multi-page-layout -/scale-pages -/crop -/extract-page -/pdf-to-single-page -/img-to-pdf -/pdf-to-img -/pdf-to-text -/pdf-to-csv -/sign -/add-password -/remove-password -/change-permissions -/add-watermark -/cert-sign -/validate-signature -/remove-cert-sign -/sanitize-pdf -/auto-redact -/redact -/stamp -/view-pdf -/add-page-numbers +/accessibilityStatement /add-image +/add-page-numbers +/add-password +/add-watermark +/adjust-contrast +/auto-redact +/auto-rename +/auto-split-pdf +/cert-sign +/change-metadata +/change-permissions +/compare +/cookiePolicy +/crop /extract-images +/extract-page /flatten +/get-info-on-pdf +/img-to-pdf +/impressum +/licenses +/merge-pdfs +/multi-page-layout +/multi-tool +/overlay-pdf +/pdf-organizer +/pdf-to-csv +/pdf-to-img +/pdf-to-single-page +/pdf-to-text +/privacyPolicy +/redact +/releases /remove-annotations /remove-blanks -/compare -/change-metadata -/get-info-on-pdf +/remove-cert-sign /remove-image-pdf +/remove-pages +/remove-password /replace-and-invert-color-pdf -/pipeline -/auto-rename -/adjust-contrast -/overlay-pdf -/auto-split-pdf -/split-pdf-by-sections -/split-pdf-by-chapters -/split-by-size-or-count +/rotate-pdf +/sanitize-pdf +/scale-pages /show-javascript +/sign +/split-by-size-or-count +/split-pdf +/split-pdf-by-chapters +/split-pdf-by-sections +/stamp /swagger-ui/index.html -/licenses -/releases -/add-attachments +/termsAndConditions +/validate-signature +/view-pdf diff --git a/testing/webpage_urls_full.txt b/testing/webpage_urls_full.txt index 86b908720..89531b0b6 100644 --- a/testing/webpage_urls_full.txt +++ b/testing/webpage_urls_full.txt @@ -1,5 +1,6 @@ / +/accessibilityStatement /add-image /add-page-numbers /add-password @@ -13,6 +14,7 @@ /change-permissions /compare /compress-pdf +/cookiePolicy /crop /extract-image-scans /extract-images @@ -22,6 +24,7 @@ /get-info-on-pdf /html-to-pdf /img-to-pdf +/impressum /licenses /markdown-to-pdf /merge-pdfs @@ -40,6 +43,7 @@ /pdf-to-word /pdf-to-xml /pipeline +/privacyPolicy /redact /releases /remove-annotations @@ -60,6 +64,7 @@ /split-pdf-by-sections /split-pdfs /stamp +/termsAndConditions /validate-signature /view-pdf -/swagger-ui/index.html \ No newline at end of file +/swagger-ui/index.html