From ea04596e3507ee7ab821e808af811eb552107ba1 Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Mon, 19 May 2025 09:11:56 +0100 Subject: [PATCH] wip - adding missing files --- .../common/configuration/AppConfig.java | 15 - .../common/configuration/PostHogConfig.java | 41 +++ .../security/SecurityConfiguration.java | 317 ++++++++++++++++ .../api/converters/ConvertToImageRequest.java | 42 +++ .../model/api/filter/FileSizeRequest.java | 19 + .../model/api/filter/PageRotationRequest.java | 19 + .../model/api/filter/PageSizeRequest.java | 20 + .../model/api/general/OverlayPdfsRequest.java | 46 +++ .../SPDF/model/api/misc/AddStampRequest.java | 87 +++++ .../SPDF/model/api/misc/MetadataRequest.java | 84 +++++ .../api/security/AddPasswordRequest.java | 62 ++++ .../api/security/ManualRedactPdfRequest.java | 31 ++ .../api/security/SanitizePdfRequest.java | 49 +++ .../SPDF/utils/RequestUriUtilsTest.java | 26 ++ .../main/templates/convert/pdf-to-text.html | 4 +- .../main/templates/fragments/card.html | 2 +- .../main/templates/fragments/navbar.html | 6 +- .../main/templates/fragments/navbarEntry.html | 2 +- .../fragments/navbarEntryCustom.html | 2 +- .../build/resources/main/templates/home.html | 346 +++++++++--------- .../software/spdf/config/WebMvcConfig.java | 10 +- .../controller/api/SplitPDFController.java | 163 +++++++++ .../api/SplitPdfByChaptersController.java | 2 +- .../api/SplitPdfBySectionsController.java | 2 +- .../api/misc/FakeScanControllerWIP.java | 311 ++++++++++++++++ .../controller/web/HomeWebController.java | 96 +++++ .../model/api/SplitPdfByChaptersRequest.java | 16 +- .../model/api/SplitPdfBySectionsRequest.java | 1 - .../model/api/security/RedactPdfRequest.java | 39 +- .../src/main/resources/templates/home.html | 346 +++++++++--------- 30 files changed, 1810 insertions(+), 396 deletions(-) create mode 100644 common/src/main/java/stirling/software/common/configuration/PostHogConfig.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/misc/AddStampRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/security/ManualRedactPdfRequest.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java create mode 100644 src/test/java/stirling/software/SPDF/utils/RequestUriUtilsTest.java create mode 100644 stirling-pdf/src/main/java/stirling/software/spdf/controller/api/SplitPDFController.java create mode 100644 stirling-pdf/src/main/java/stirling/software/spdf/controller/api/misc/FakeScanControllerWIP.java create mode 100644 stirling-pdf/src/main/java/stirling/software/spdf/controller/web/HomeWebController.java diff --git a/common/src/main/java/stirling/software/common/configuration/AppConfig.java b/common/src/main/java/stirling/software/common/configuration/AppConfig.java index 51926ab4d..5daf0da9e 100644 --- a/common/src/main/java/stirling/software/common/configuration/AppConfig.java +++ b/common/src/main/java/stirling/software/common/configuration/AppConfig.java @@ -1,6 +1,5 @@ package stirling.software.common.configuration; -import com.posthog.java.PostHog; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -48,12 +47,6 @@ public class AppConfig { @Value("${server.port:8080}") private String serverPort; - @Value("${posthog.apiKey") - private String posthogApiKey; - - @Value("${posthog.host}") - private String posthogHost; - @Bean @ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true") public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) { @@ -277,12 +270,4 @@ public class AppConfig { return "Unknown"; } } - - @Bean - public PostHog postHog() { - return new PostHog.Builder(posthogApiKey) - .host(posthogHost) - .logger(new PostHogLoggerImpl()) - .build(); - } } diff --git a/common/src/main/java/stirling/software/common/configuration/PostHogConfig.java b/common/src/main/java/stirling/software/common/configuration/PostHogConfig.java new file mode 100644 index 000000000..589b5cac9 --- /dev/null +++ b/common/src/main/java/stirling/software/common/configuration/PostHogConfig.java @@ -0,0 +1,41 @@ +package stirling.software.common.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.posthog.java.PostHog; + +import jakarta.annotation.PreDestroy; + +import lombok.extern.slf4j.Slf4j; + +@Configuration +@Slf4j +public class PostHogConfig { + + @Value("${posthog.api.key}") + private String posthogApiKey; + + @Value("${posthog.host}") + private String posthogHost; + + private PostHog postHogClient; + + @Bean + public PostHog postHogClient() { + postHogClient = + new PostHog.Builder(posthogApiKey) + .host(posthogHost) + .logger(new PostHogLoggerImpl()) + .build(); + return postHogClient; + } + + @PreDestroy + public void shutdownPostHog() { + if (postHogClient != null) { + postHogClient.shutdown(); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java new file mode 100644 index 000000000..a4c10d1ae --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -0,0 +1,317 @@ +package stirling.software.SPDF.config.security; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.security.web.savedrequest.NullRequestCache; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import lombok.extern.slf4j.Slf4j; + +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler; +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler; +import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter; +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.JPATokenRepositoryImpl; +import stirling.software.SPDF.repository.PersistentLoginRepository; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +@Slf4j +@DependsOn("runningProOrHigher") +public class SecurityConfiguration { + + private final CustomUserDetailsService userDetailsService; + private final UserService userService; + private final boolean loginEnabledValue; + private final boolean runningProOrHigher; + + private final ApplicationProperties applicationProperties; + private final UserAuthenticationFilter userAuthenticationFilter; + private final LoginAttemptService loginAttemptService; + private final FirstLoginFilter firstLoginFilter; + private final SessionPersistentRegistry sessionRegistry; + private final PersistentLoginRepository persistentLoginRepository; + private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper; + private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations; + private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver; + + public SecurityConfiguration( + PersistentLoginRepository persistentLoginRepository, + CustomUserDetailsService userDetailsService, + @Lazy UserService userService, + @Qualifier("loginEnabled") boolean loginEnabledValue, + @Qualifier("runningProOrHigher") boolean runningProOrHigher, + ApplicationProperties applicationProperties, + UserAuthenticationFilter userAuthenticationFilter, + LoginAttemptService loginAttemptService, + FirstLoginFilter firstLoginFilter, + SessionPersistentRegistry sessionRegistry, + @Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper, + @Autowired(required = false) + RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations, + @Autowired(required = false) + OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) { + this.userDetailsService = userDetailsService; + this.userService = userService; + this.loginEnabledValue = loginEnabledValue; + this.runningProOrHigher = runningProOrHigher; + this.applicationProperties = applicationProperties; + this.userAuthenticationFilter = userAuthenticationFilter; + this.loginAttemptService = loginAttemptService; + this.firstLoginFilter = firstLoginFilter; + this.sessionRegistry = sessionRegistry; + this.persistentLoginRepository = persistentLoginRepository; + this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper; + this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations; + this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) { + http.csrf(csrf -> csrf.disable()); + } + + if (loginEnabledValue) { + http.addFilterBefore( + userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + if (!applicationProperties.getSecurity().getCsrfDisabled()) { + CookieCsrfTokenRepository cookieRepo = + CookieCsrfTokenRepository.withHttpOnlyFalse(); + CsrfTokenRequestAttributeHandler requestHandler = + new CsrfTokenRequestAttributeHandler(); + requestHandler.setCsrfRequestAttributeName(null); + http.csrf( + csrf -> + csrf.ignoringRequestMatchers( + request -> { + String apiKey = request.getHeader("X-API-KEY"); + // If there's no API key, don't ignore CSRF + // (return false) + if (apiKey == null || apiKey.trim().isEmpty()) { + return false; + } + // Validate API key using existing UserService + try { + Optional user = + userService.getUserByApiKey(apiKey); + // If API key is valid, ignore CSRF (return + // true) + // If API key is invalid, don't ignore CSRF + // (return false) + return user.isPresent(); + } catch (Exception e) { + // If there's any error validating the API + // key, don't ignore CSRF + return false; + } + }) + .csrfTokenRepository(cookieRepo) + .csrfTokenRequestHandler(requestHandler)); + } + http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); + http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); + http.sessionManagement( + sessionManagement -> + sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) + .maximumSessions(10) + .maxSessionsPreventsLogin(false) + .sessionRegistry(sessionRegistry) + .expiredUrl("/login?logout=true")); + http.authenticationProvider(daoAuthenticationProvider()); + http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); + http.logout( + logout -> + logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessHandler( + new CustomLogoutSuccessHandler(applicationProperties)) + .clearAuthentication(true) + .invalidateHttpSession(true) + .deleteCookies("JSESSIONID", "remember-me")); + http.rememberMe( + rememberMeConfigurer -> // Use the configurator directly + rememberMeConfigurer + .tokenRepository(persistentTokenRepository()) + .tokenValiditySeconds( // 14 days + 14 * 24 * 60 * 60) + .userDetailsService( // Your existing UserDetailsService + userDetailsService) + .useSecureCookie( // Enable secure cookie + true) + .rememberMeParameter( // Form parameter name + "remember-me") + .rememberMeCookieName( // Cookie name + "remember-me") + .alwaysRemember(false)); + http.authorizeHttpRequests( + authz -> + authz.requestMatchers( + req -> { + String uri = req.getRequestURI(); + String contextPath = req.getContextPath(); + // Remove the context path from the URI + String trimmedUri = + uri.startsWith(contextPath) + ? uri.substring( + contextPath.length()) + : uri; + return trimmedUri.startsWith("/login") + || trimmedUri.startsWith("/oauth") + || trimmedUri.startsWith("/saml2") + || trimmedUri.endsWith(".svg") + || trimmedUri.startsWith("/register") + || trimmedUri.startsWith("/error") + || trimmedUri.startsWith("/images/") + || trimmedUri.startsWith("/public/") + || trimmedUri.startsWith("/css/") + || trimmedUri.startsWith("/fonts/") + || trimmedUri.startsWith("/js/") + || trimmedUri.startsWith( + "/api/v1/info/status"); + }) + .permitAll() + .anyRequest() + .authenticated()); + // Handle User/Password Logins + if (applicationProperties.getSecurity().isUserPass()) { + http.formLogin( + formLogin -> + formLogin + .loginPage("/login") + .successHandler( + new CustomAuthenticationSuccessHandler( + loginAttemptService, userService)) + .failureHandler( + new CustomAuthenticationFailureHandler( + loginAttemptService, userService)) + .defaultSuccessUrl("/") + .permitAll()); + } + // Handle OAUTH2 Logins + if (applicationProperties.getSecurity().isOauth2Active()) { + http.oauth2Login( + oauth2 -> + oauth2.loginPage("/oauth2") + . + /* + This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. + If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser' + is set as true, else login fails with an error message advising the same. + */ + successHandler( + new CustomOAuth2AuthenticationSuccessHandler( + loginAttemptService, + applicationProperties, + userService)) + .failureHandler( + new CustomOAuth2AuthenticationFailureHandler()) + . // Add existing Authorities from the database + userInfoEndpoint( + userInfoEndpoint -> + userInfoEndpoint + .oidcUserService( + new CustomOAuth2UserService( + applicationProperties, + userService, + loginAttemptService)) + .userAuthoritiesMapper( + oAuth2userAuthoritiesMapper)) + .permitAll()); + } + // Handle SAML + if (applicationProperties.getSecurity().isSaml2Active() && runningProOrHigher) { + // Configure the authentication provider + OpenSaml4AuthenticationProvider authenticationProvider = + new OpenSaml4AuthenticationProvider(); + authenticationProvider.setResponseAuthenticationConverter( + new CustomSaml2ResponseAuthenticationConverter(userService)); + http.authenticationProvider(authenticationProvider) + .saml2Login( + saml2 -> { + try { + saml2.loginPage("/saml2") + .relyingPartyRegistrationRepository( + saml2RelyingPartyRegistrations) + .authenticationManager( + new ProviderManager(authenticationProvider)) + .successHandler( + new CustomSaml2AuthenticationSuccessHandler( + loginAttemptService, + applicationProperties, + userService)) + .failureHandler( + new CustomSaml2AuthenticationFailureHandler()) + .authenticationRequestResolver( + saml2AuthenticationRequestResolver); + } catch (Exception e) { + log.error("Error configuring SAML 2 login", e); + throw new RuntimeException(e); + } + }); + } + } else { + log.debug("SAML 2 login is not enabled. Using default."); + http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + } + return http.build(); + } + + public DaoAuthenticationProvider daoAuthenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + @Bean + public IPRateLimitingFilter rateLimitingFilter() { + // Example limit TODO add config level + int maxRequestsPerIp = 1000000; + return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); + } + + @Bean + public PersistentTokenRepository persistentTokenRepository() { + return new JPATokenRepositoryImpl(persistentLoginRepository); + } + + @Bean + public boolean activeSecurity() { + return true; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java new file mode 100644 index 000000000..149676946 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java @@ -0,0 +1,42 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFWithPageNums; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ConvertToImageRequest extends PDFWithPageNums { + + @Schema( + description = "The output image format", + defaultValue = "png", + allowableValues = {"png", "jpeg", "jpg", "gif", "webp"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String imageFormat; + + @Schema( + description = + "Choose between a single image containing all pages or separate images for each" + + " page", + defaultValue = "multiple", + allowableValues = {"single", "multiple"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String singleOrMultiple; + + @Schema( + description = "The color type of the output image(s)", + defaultValue = "color", + allowableValues = {"color", "greyscale", "blackwhite"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String colorType; + + @Schema( + description = "The DPI (dots per inch) for the output image(s)", + defaultValue = "300", + requiredMode = Schema.RequiredMode.REQUIRED) + private Integer dpi; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java new file mode 100644 index 000000000..a3c57077d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java @@ -0,0 +1,19 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper = true) +public class FileSizeRequest extends PDFComparison { + + @Schema( + description = "Size of the file in bytes", + requiredMode = Schema.RequiredMode.REQUIRED, + defaultValue = "0") + private long fileSize; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java new file mode 100644 index 000000000..05fd10c31 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java @@ -0,0 +1,19 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper = true) +public class PageRotationRequest extends PDFComparison { + + @Schema( + description = "Rotation in degrees", + requiredMode = Schema.RequiredMode.REQUIRED, + defaultValue = "0") + private int rotation; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java new file mode 100644 index 000000000..2fa74f040 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java @@ -0,0 +1,20 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper = true) +public class PageSizeRequest extends PDFComparison { + + @Schema( + description = "Standard Page Size", + allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL"}, + defaultValue = "A4", + requiredMode = Schema.RequiredMode.REQUIRED) + private String standardPageSize; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java new file mode 100644 index 000000000..528e57844 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java @@ -0,0 +1,46 @@ +package stirling.software.SPDF.model.api.general; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class OverlayPdfsRequest extends PDFFile { + + @Schema( + description = + "An array of PDF files to be used as overlays on the base PDF. The order in" + + " these files is applied based on the selected mode.", + requiredMode = Schema.RequiredMode.REQUIRED) + private MultipartFile[] overlayFiles; + + @Schema( + description = + "The mode of overlaying: 'SequentialOverlay' for sequential application," + + " 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay'" + + " for fixed repetition based on provided counts", + allowableValues = {"SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String overlayMode; + + @Schema( + description = + "An array of integers specifying the number of times each corresponding overlay" + + " file should be applied in the 'FixedRepeatOverlay' mode. This should" + + " match the length of the overlayFiles array.", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private int[] counts; + + @Schema( + description = "Overlay position 0 is Foregound, 1 is Background", + allowableValues = {"0", "1"}, + requiredMode = Schema.RequiredMode.REQUIRED, + type = "number") + private int overlayPosition; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AddStampRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AddStampRequest.java new file mode 100644 index 000000000..48d470a5a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AddStampRequest.java @@ -0,0 +1,87 @@ +package stirling.software.SPDF.model.api.misc; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFWithPageNums; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AddStampRequest extends PDFWithPageNums { + + @Schema( + description = "The stamp type (text or image)", + allowableValues = {"text", "image"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String stampType; + + @Schema(description = "The stamp text", defaultValue = "Stirling Software") + private String stampText; + + @Schema(description = "The stamp image") + private MultipartFile stampImage; + + @Schema( + description = "The selected alphabet of the stamp text", + allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"}, + defaultValue = "roman") + private String alphabet = "roman"; + + @Schema( + description = "The font size of the stamp text and image", + defaultValue = "30", + requiredMode = Schema.RequiredMode.REQUIRED) + private float fontSize; + + @Schema( + description = "The rotation of the stamp in degrees", + defaultValue = "0", + requiredMode = Schema.RequiredMode.REQUIRED) + private float rotation; + + @Schema( + description = "The opacity of the stamp (0.0 - 1.0)", + defaultValue = "0.5", + requiredMode = Schema.RequiredMode.REQUIRED) + private float opacity; + + @Schema( + description = + "Position for stamp placement based on a 1-9 grid (1: bottom-left, 2: bottom-center," + + " 3: bottom-right, 4: middle-left, 5: middle-center, 6: middle-right," + + " 7: top-left, 8: top-center, 9: top-right)", + allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}, + defaultValue = "5", + requiredMode = Schema.RequiredMode.REQUIRED) + private int position; + + @Schema( + description = + "Override X coordinate for stamp placement. If set, it will override the" + + " position-based calculation. Negative value means no override.", + defaultValue = "-1", + requiredMode = Schema.RequiredMode.REQUIRED) + private float overrideX; // Default to -1 indicating no override + + @Schema( + description = + "Override Y coordinate for stamp placement. If set, it will override the" + + " position-based calculation. Negative value means no override.", + defaultValue = "-1", + requiredMode = Schema.RequiredMode.REQUIRED) + private float overrideY; // Default to -1 indicating no override + + @Schema( + description = "Specifies the margin size for the stamp.", + allowableValues = {"small", "medium", "large", "x-large"}, + defaultValue = "medium", + requiredMode = Schema.RequiredMode.REQUIRED) + private String customMargin; + + @Schema(description = "The color of the stamp text", defaultValue = "#d3d3d3") + private String customColor; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java new file mode 100644 index 000000000..0238e9607 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java @@ -0,0 +1,84 @@ +package stirling.software.SPDF.model.api.misc; + +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class MetadataRequest extends PDFFile { + + @Schema( + description = "Delete all metadata if set to true", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean deleteAll; + + @Schema( + description = "The author of the document", + defaultValue = "author", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String author; + + @Schema( + description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)", + pattern = "yyyy/MM/dd HH:mm:ss", + defaultValue = "2023/10/01 12:00:00", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String creationDate; + + @Schema( + description = "The creator of the document", + defaultValue = "creator", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String creator; + + @Schema( + description = "The keywords for the document", + defaultValue = "keywords", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String keywords; + + @Schema( + description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)", + pattern = "yyyy/MM/dd HH:mm:ss", + defaultValue = "2023/10/01 12:00:00", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String modificationDate; + + @Schema( + description = "The producer of the document", + defaultValue = "producer", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String producer; + + @Schema( + description = "The subject of the document", + defaultValue = "subject", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String subject; + + @Schema( + description = "The title of the document", + defaultValue = "title", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String title; + + @Schema( + description = "The trapped status of the document", + defaultValue = "False", + allowableValues = {"True", "False", "Unknown"}, + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String trapped; + + @Schema( + description = + "Map list of key and value of custom parameters. Note these must start with" + + " customKey and customValue if they are non-standard") + private Map allRequestParams; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java new file mode 100644 index 000000000..8dfa4e54f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java @@ -0,0 +1,62 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AddPasswordRequest extends PDFFile { + + @Schema( + description = + "The owner password to be added to the PDF file (Restricts what can be done" + + " with the document once it is opened)", + format = "password") + private String ownerPassword; + + @Schema( + description = + "The password to be added to the PDF file (Restricts the opening of the" + + " document itself.)", + format = "password") + private String password; + + @Schema( + description = "The length of the encryption key", + allowableValues = {"40", "128", "256"}, + defaultValue = "256", + requiredMode = Schema.RequiredMode.REQUIRED) + private int keyLength = 256; + + @Schema(description = "Whether document assembly is prevented", defaultValue = "false") + private Boolean preventAssembly; + + @Schema(description = "Whether content extraction is prevented", defaultValue = "false") + private Boolean preventExtractContent; + + @Schema( + description = "Whether content extraction for accessibility is prevented", + defaultValue = "false") + private Boolean preventExtractForAccessibility; + + @Schema(description = "Whether form filling is prevented", defaultValue = "false") + private Boolean preventFillInForm; + + @Schema(description = "Whether document modification is prevented", defaultValue = "false") + private Boolean preventModify; + + @Schema( + description = "Whether modification of annotations is prevented", + defaultValue = "false") + private Boolean preventModifyAnnotations; + + @Schema(description = "Whether printing of the document is prevented", defaultValue = "false") + private Boolean preventPrinting; + + @Schema(description = "Whether faithful printing is prevented", defaultValue = "false") + private Boolean preventPrintingFaithful; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/ManualRedactPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/ManualRedactPdfRequest.java new file mode 100644 index 000000000..bcc715d16 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/ManualRedactPdfRequest.java @@ -0,0 +1,31 @@ +package stirling.software.SPDF.model.api.security; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFWithPageNums; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ManualRedactPdfRequest extends PDFWithPageNums { + @Schema( + description = "A list of areas that should be redacted", + requiredMode = Schema.RequiredMode.REQUIRED) + private List redactions; + + @Schema( + description = "Convert the redacted PDF to an image", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean convertPDFToImage; + + @Schema( + description = "The color used to fully redact certain pages", + defaultValue = "#000000", + requiredMode = Schema.RequiredMode.REQUIRED) + private String pageRedactionColor; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java new file mode 100644 index 000000000..dc38b9fc8 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java @@ -0,0 +1,49 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SanitizePdfRequest extends PDFFile { + + @Schema( + description = "Remove JavaScript actions from the PDF", + defaultValue = "true", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeJavaScript; + + @Schema( + description = "Remove embedded files from the PDF", + defaultValue = "true", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeEmbeddedFiles; + + @Schema( + description = "Remove XMP metadata from the PDF", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeXMPMetadata; + + @Schema( + description = "Remove document info metadata from the PDF", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeMetadata; + + @Schema( + description = "Remove links from the PDF", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeLinks; + + @Schema( + description = "Remove fonts from the PDF", + defaultValue = "false", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean removeFonts; +} diff --git a/src/test/java/stirling/software/SPDF/utils/RequestUriUtilsTest.java b/src/test/java/stirling/software/SPDF/utils/RequestUriUtilsTest.java new file mode 100644 index 000000000..f18196030 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/utils/RequestUriUtilsTest.java @@ -0,0 +1,26 @@ +package stirling.software.SPDF.utils; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class RequestUriUtilsTest { + + @Test + public void testIsStaticResource() { + assertTrue(RequestUriUtils.isStaticResource("/css/styles.css")); + assertTrue(RequestUriUtils.isStaticResource("/js/script.js")); + assertTrue(RequestUriUtils.isStaticResource("/images/logo.png")); + assertTrue(RequestUriUtils.isStaticResource("/public/index.html")); + assertTrue(RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js")); + assertTrue(RequestUriUtils.isStaticResource("/api/v1/info/status")); + assertTrue(RequestUriUtils.isStaticResource("/some-path/icon.svg")); + assertFalse(RequestUriUtils.isStaticResource("/api/v1/users")); + assertFalse(RequestUriUtils.isStaticResource("/api/v1/orders")); + assertFalse(RequestUriUtils.isStaticResource("/")); + assertTrue(RequestUriUtils.isStaticResource("/login")); + assertFalse(RequestUriUtils.isStaticResource("/register")); + assertFalse(RequestUriUtils.isStaticResource("/api/v1/products")); + } +} diff --git a/stirling-pdf/build/resources/main/templates/convert/pdf-to-text.html b/stirling-pdf/build/resources/main/templates/convert/pdf-to-text.html index a98f06f0f..2f4f9fb1e 100644 --- a/stirling-pdf/build/resources/main/templates/convert/pdf-to-text.html +++ b/stirling-pdf/build/resources/main/templates/convert/pdf-to-text.html @@ -22,13 +22,13 @@
-

+

diff --git a/stirling-pdf/build/resources/main/templates/fragments/card.html b/stirling-pdf/build/resources/main/templates/fragments/card.html index b2ab67eec..dfdcc2ade 100644 --- a/stirling-pdf/build/resources/main/templates/fragments/card.html +++ b/stirling-pdf/build/resources/main/templates/fragments/card.html @@ -1,4 +1,4 @@ -