diff --git a/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java b/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java index 074f422000..9649696567 100644 --- a/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java +++ b/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java @@ -5,6 +5,8 @@ public interface UserServiceInterface { String getCurrentUsername(); + String getCurrentUserApiKey(); + long getTotalUsersCount(); boolean isCurrentUserAdmin(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java index 1eac93b227..4d43faf629 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java @@ -37,7 +37,6 @@ import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineResult; import stirling.software.SPDF.service.ApiDocService; -import stirling.software.common.model.enumeration.Role; import stirling.software.common.service.UserServiceInterface; import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; @@ -84,9 +83,35 @@ public class PipelineProcessor { return name.substring(0, underscoreIndex) + extension; } + // Allowlist of URL path prefixes permitted through the pipeline. + private static final List ALLOWED_PIPELINE_PATH_PREFIXES = + List.of( + "/api/v1/general/", + "/api/v1/misc/", + "/api/v1/security/", + "/api/v1/convert/", + "/api/v1/filter/"); + + private void validatePipelineUrl(String url) { + // Strip scheme+host to get the path portion for comparison + String path = url; + int schemeEnd = url.indexOf("://"); + if (schemeEnd != -1) { + int pathStart = url.indexOf('/', schemeEnd + 3); + path = pathStart != -1 ? url.substring(pathStart) : "/"; + } + final String pathToCheck = path; + boolean allowed = ALLOWED_PIPELINE_PATH_PREFIXES.stream().anyMatch(pathToCheck::contains); + if (!allowed) { + log.warn("Blocked pipeline request to disallowed URL: {}", url); + throw new SecurityException( + "Pipeline operation not permitted for endpoint: " + pathToCheck); + } + } + private String getApiKeyForUser() { if (userService == null) return ""; - return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); + return userService.getCurrentUserApiKey(); } private String getBaseUrl() { @@ -283,6 +308,7 @@ public class PipelineProcessor { /* package */ ResponseEntity sendWebRequest( String url, MultiValueMap body) { + validatePipelineUrl(url); RestTemplate restTemplate = new RestTemplate(); // Set up headers, including API key HttpHeaders headers = new HttpHeaders(); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessorTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessorTest.java index d58770f45d..6b6ed82976 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessorTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessorTest.java @@ -205,7 +205,8 @@ class PipelineProcessorTest { }); })) { ResponseEntity response = - pipelineProcessor.sendWebRequest("http://localhost/api", body); + pipelineProcessor.sendWebRequest( + "http://localhost/api/v1/general/merge-pdfs", body); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java index ea960207dd..e18c101e1e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java @@ -198,6 +198,15 @@ public class UserService implements UserServiceInterface { return user.getApiKey(); } + @Override + public String getCurrentUserApiKey() { + String username = getCurrentUsername(); + if (username == null || username.isEmpty()) { + throw new IllegalStateException("Cannot determine calling user for API key lookup"); + } + return getApiKeyForUser(username); + } + public boolean isValidApiKey(String apiKey) { return userRepository.findByApiKey(apiKey).isPresent(); } diff --git a/build.gradle b/build.gradle index b41c54e3b8..5acd760592 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ ext { springSecuritySamlVersion = "7.0.2" openSamlVersion = "5.2.1" commonmarkVersion = "0.27.1" - googleJavaFormatVersion = "1.35.0" + googleJavaFormatVersion = "1.28.0" logback = "1.5.32" junitPlatformVersion = "1.12.2" modernJavaVersion = 21 @@ -78,7 +78,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.8.0' + version = '2.9.0' configurations.configureEach { exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 44ef827c3f..84acfeba8a 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling-PDF", - "version": "2.8.0", + "version": "2.9.0", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", diff --git a/frontend/src/core/testing/serverExperienceSimulations.ts b/frontend/src/core/testing/serverExperienceSimulations.ts index bbd9b1702a..06bd8fdc76 100644 --- a/frontend/src/core/testing/serverExperienceSimulations.ts +++ b/frontend/src/core/testing/serverExperienceSimulations.ts @@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.8.0', + appVersion: '2.9.0', serverCertificateEnabled: false, enableAlphaFunctionality: false, serverPort: 8080, diff --git a/frontend/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/src/proprietary/testing/serverExperienceSimulations.ts index 4177f296ee..d7087533f4 100644 --- a/frontend/src/proprietary/testing/serverExperienceSimulations.ts +++ b/frontend/src/proprietary/testing/serverExperienceSimulations.ts @@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.8.0', + appVersion: '2.9.0', serverCertificateEnabled: false, enableAlphaFunctionality: false, enableDesktopInstallSlide: true,