From e24a30828b5adb09cee83b36ba9d814fd75a82b5 Mon Sep 17 00:00:00 2001 From: brios <127139797+balazs-szucs@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:49:59 +0200 Subject: [PATCH] refactor(api): replace deprecated APIs with their modern equivalents (#6434) # Description of Changes This PR resolves deprecation warnings and addresses compiler errors resulting from the transition to Spring Security 7.x., as well Jackson 3 and general Java. * Replaced all usages of `asText()`/`isTextual()` with `asString()`/`isString()` in JSON parsing logic across `FormPayloadParser.java`, `ApiEndpoint.java`, and `KeygenLicenseVerifier.java` to ensure consistent and type-safe string * Updated `CustomSaml2AuthenticatedPrincipal` to implement `Saml2ResponseAssertionAccessor`, added a `responseValue` field, and provided additional getter methods and type-safe attribute accessors. * Switched from constructing `URL` objects directly from strings to using `URI.create(...).toURL()` in `UIDataTessdataController.java` for improved URL safety and parsing. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../api/form/FormPayloadParser.java | 16 +++---- .../software/SPDF/model/ApiEndpoint.java | 4 +- .../ee/KeygenLicenseVerifier.java | 42 +++++++++---------- .../api/UIDataTessdataController.java | 5 ++- .../CustomSaml2AuthenticatedPrincipal.java | 37 ++++++++++++++-- ...mSaml2ResponseAuthenticationConverter.java | 6 ++- .../service/AiToolInputValidator.java | 2 +- .../service/AiWorkflowService.java | 4 +- .../storage/service/FileStorageService.java | 6 +-- .../service/SigningFinalizationService.java | 4 +- .../ProprietaryUIDataControllerMoreTest.java | 2 +- .../service/AiToolInputValidatorTest.java | 2 +- 12 files changed, 83 insertions(+), 47 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormPayloadParser.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormPayloadParser.java index 6c0c40ef0a..6f82c7546e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormPayloadParser.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormPayloadParser.java @@ -117,8 +117,8 @@ final class FormPayloadParser { names.add(single); } } - } else if (root.isTextual()) { - final String single = trimToNull(root.asText("")); + } else if (root.isString()) { + final String single = trimToNull(root.asString("")); if (single != null) { names.add(single); } @@ -197,8 +197,8 @@ final class FormPayloadParser { if (node == null || node.isNull()) { return null; } - if (node.isTextual()) { - return trimToEmpty(node.asText("")); + if (node.isString()) { + return trimToEmpty(node.asString("")); } if (node.isNumber()) { return node.numberValue().toString(); @@ -207,7 +207,7 @@ final class FormPayloadParser { return Boolean.toString(node.booleanValue()); } // Fallback for other scalar-like nodes - return trimToEmpty(node.asText("")); + return trimToEmpty(node.asString("")); } private static void collectNames(JsonNode arrayNode, Set sink) { @@ -227,8 +227,8 @@ final class FormPayloadParser { return null; } - if (node.isTextual()) { - return trimToNull(node.asText("")); + if (node.isString()) { + return trimToNull(node.asString("")); } if (node.isObject()) { @@ -269,7 +269,7 @@ final class FormPayloadParser { final JsonNode v = objectNode.get(key); if (v == null || v.isNull()) { result.put(key, null); - } else if (v.isTextual() || v.isNumber() || v.isBoolean()) { + } else if (v.isString() || v.isNumber() || v.isBoolean()) { result.put(key, coerceScalarToString(v)); } else { result.put(key, v.toString()); diff --git a/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java b/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java index 04d2bb1fa1..e25b0dde8e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java @@ -18,10 +18,10 @@ public class ApiEndpoint { postNode.path("parameters") .forEach( paramNode -> { - String paramName = paramNode.path("name").asText(""); + String paramName = paramNode.path("name").asString(""); parameters.put(paramName, paramNode); }); - this.description = postNode.path("description").asText(""); + this.description = postNode.path("description").asString(""); } public boolean areParametersValid(Map providedParams) { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java index 1680aa9285..bc68a64f6e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java @@ -134,9 +134,9 @@ public class KeygenLicenseVerifier { try { JsonNode attrs = objectMapper.readTree(payload); - encryptedData = attrs.path("enc").asText(""); - encodedSignature = attrs.path("sig").asText(""); - algorithm = attrs.path("alg").asText(""); + encryptedData = attrs.path("enc").asString(""); + encodedSignature = attrs.path("sig").asString(""); + algorithm = attrs.path("alg").asString(""); } catch (Exception e) { log.error("Failed to parse license file: {}", e.getMessage()); return false; @@ -219,11 +219,11 @@ public class KeygenLicenseVerifier { String issuedStr = metaObj.path("issued").isNull() ? null - : metaObj.path("issued").asText(null); + : metaObj.path("issued").asString(null); String expiryStr = metaObj.path("expiry").isNull() ? null - : metaObj.path("expiry").asText(null); + : metaObj.path("expiry").asString(null); if (issuedStr != null && expiryStr != null) { java.time.Instant issued = java.time.Instant.parse(issuedStr); @@ -287,7 +287,7 @@ public class KeygenLicenseVerifier { } // Check license status if available - String status = attributesObj.path("status").asText(null); + String status = attributesObj.path("status").asString(null); if (status != null && !"ACTIVE".equals(status) && !"EXPIRING".equals(status)) { // Accept "EXPIRING" status as valid @@ -381,7 +381,7 @@ public class KeygenLicenseVerifier { JsonNode licenseObj = licenseData.path("license"); if (licenseObj.isMissingNode() || !licenseObj.isObject()) { - String id = licenseData.path("id").asText(null); + String id = licenseData.path("id").asString(null); if (id != null) { log.info("Found license ID: {}", id); licenseObj = licenseData; // Use the root object as the license object @@ -391,7 +391,7 @@ public class KeygenLicenseVerifier { } } - String licenseId = licenseObj.path("id").asText("unknown"); + String licenseId = licenseObj.path("id").asString("unknown"); log.info("Processing license with ID: {}", licenseId); // Check for floating license in license object @@ -402,7 +402,7 @@ public class KeygenLicenseVerifier { } // Check expiry date - String expiryStr = licenseObj.path("expiry").asText(null); + String expiryStr = licenseObj.path("expiry").asString(null); if (expiryStr != null && !"null".equals(expiryStr)) { java.time.Instant expiry = java.time.Instant.parse(expiryStr); java.time.Instant now = java.time.Instant.now(); @@ -420,7 +420,7 @@ public class KeygenLicenseVerifier { // Extract account, product, policy info JsonNode accountObj = licenseData.path("account"); if (!accountObj.isMissingNode() && accountObj.isObject()) { - String accountId = accountObj.path("id").asText("unknown"); + String accountId = accountObj.path("id").asString("unknown"); log.info("License belongs to account: {}", accountId); // Verify this matches your expected account ID @@ -433,7 +433,7 @@ public class KeygenLicenseVerifier { // Extract policy information if available JsonNode policyObj = licenseData.path("policy"); if (!policyObj.isMissingNode() && policyObj.isObject()) { - String policyId = policyObj.path("id").asText("unknown"); + String policyId = policyObj.path("id").asString("unknown"); log.info("License uses policy: {}", policyId); // Check for floating license in policy @@ -503,9 +503,9 @@ public class KeygenLicenseVerifier { validateLicense(licenseKey, machineFingerprint, context); if (validationResponse != null) { boolean isValid = validationResponse.path("meta").path("valid").asBoolean(); - String licenseId = validationResponse.path("data").path("id").asText(""); + String licenseId = validationResponse.path("data").path("id").asString(""); if (!isValid) { - String code = validationResponse.path("meta").path("code").asText(""); + String code = validationResponse.path("meta").path("code").asString(""); log.info(code); if ("NO_MACHINE".equals(code) || "NO_MACHINES".equals(code) @@ -589,8 +589,8 @@ public class KeygenLicenseVerifier { JsonNode metaNode = jsonResponse.path("meta"); boolean isValid = metaNode.path("valid").asBoolean(); - String detail = metaNode.path("detail").asText(""); - String code = metaNode.path("code").asText(""); + String detail = metaNode.path("detail").asString(""); + String code = metaNode.path("code").asString(""); log.info("License validity: {}", isValid); log.info("Validation detail: {}", detail); @@ -614,7 +614,7 @@ public class KeygenLicenseVerifier { if (includedNode.isArray()) { for (JsonNode node : includedNode) { - if ("policies".equals(node.path("type").asText(""))) { + if ("policies".equals(node.path("type").asString(""))) { policyNode = node; break; } @@ -700,9 +700,9 @@ public class KeygenLicenseVerifier { for (JsonNode machine : machines) { if (machineFingerprint.equals( - machine.path("attributes").path("fingerprint").asText(""))) { + machine.path("attributes").path("fingerprint").asString(""))) { isCurrentMachineActivated = true; - currentMachineId = machine.path("id").asText(""); + currentMachineId = machine.path("id").asString(""); log.info( "Current machine is already activated with ID: {}", currentMachineId); @@ -729,14 +729,14 @@ public class KeygenLicenseVerifier { for (JsonNode machine : machines) { String createdStr = - machine.path("attributes").path("created").asText(null); + machine.path("attributes").path("created").asString(null); if (createdStr != null && !createdStr.isEmpty()) { try { java.time.Instant createdTime = java.time.Instant.parse(createdStr); if (oldestTime == null || createdTime.isBefore(oldestTime)) { oldestTime = createdTime; - oldestMachineId = machine.path("id").asText(""); + oldestMachineId = machine.path("id").asString(""); } } catch (Exception e) { log.warn( @@ -750,7 +750,7 @@ public class KeygenLicenseVerifier { if (oldestMachineId == null) { log.warn( "Could not determine oldest machine by timestamp, using first machine in list"); - oldestMachineId = machines.path(0).path("id").asText(""); + oldestMachineId = machines.path(0).path("id").asString(""); } log.info("Deregistering machine with ID: {}", oldestMachineId); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UIDataTessdataController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UIDataTessdataController.java index 8212dcfb29..3aba9d0eb9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UIDataTessdataController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UIDataTessdataController.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.security.controller.api; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -150,7 +151,7 @@ public class UIDataTessdataController { protected boolean downloadLanguageFile(String safeLang, Path targetFile, String downloadUrl) { HttpURLConnection connection = null; try { - URL url = new URL(downloadUrl); + URL url = URI.create(downloadUrl).toURL(); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("User-Agent", "Stirling-PDF-App"); @@ -199,7 +200,7 @@ public class UIDataTessdataController { String apiUrl = "https://api.github.com/repos/tesseract-ocr/tessdata/contents"; HttpURLConnection connection = null; try { - URL url = new URL(apiUrl); + URL url = URI.create(apiUrl).toURL(); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("User-Agent", "Stirling-PDF-App"); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticatedPrincipal.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticatedPrincipal.java index a39a390927..c58d2dcc73 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticatedPrincipal.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticatedPrincipal.java @@ -5,15 +5,17 @@ import java.util.List; import java.util.Map; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.core.AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public record CustomSaml2AuthenticatedPrincipal( String name, Map> attributes, String nameId, - List sessionIndexes) - implements Saml2AuthenticatedPrincipal, Serializable { + List sessionIndexes, + String responseValue) + implements Saml2ResponseAssertionAccessor, AuthenticatedPrincipal, Serializable { @Override public String getName() { @@ -24,4 +26,33 @@ public record CustomSaml2AuthenticatedPrincipal( public Map> getAttributes() { return this.attributes; } + + @Override + public String getNameId() { + return this.nameId; + } + + @Override + public List getSessionIndexes() { + return this.sessionIndexes; + } + + @Override + public String getResponseValue() { + return this.responseValue; + } + + @Override + @SuppressWarnings("unchecked") + public List getAttribute(String name) { + List values = this.attributes.get(name); + return values != null ? (List) values : null; + } + + @Override + @SuppressWarnings("unchecked") + public A getFirstAttribute(String name) { + List values = this.attributes.get(name); + return values != null && !values.isEmpty() ? (A) values.get(0) : null; + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2ResponseAuthenticationConverter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2ResponseAuthenticationConverter.java index f95e2cbc25..0acc98ee55 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2ResponseAuthenticationConverter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2ResponseAuthenticationConverter.java @@ -99,7 +99,11 @@ public class CustomSaml2ResponseAuthenticationConverter CustomSaml2AuthenticatedPrincipal principal = new CustomSaml2AuthenticatedPrincipal( - userIdentifier, attributes, userIdentifier, sessionIndexes); + userIdentifier, + attributes, + userIdentifier, + sessionIndexes, + responseToken.getToken().getSaml2Response()); return new Saml2Authentication( principal, diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/AiToolInputValidator.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/AiToolInputValidator.java index 260d69fb22..b2d389be3e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/AiToolInputValidator.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/AiToolInputValidator.java @@ -39,7 +39,7 @@ public final class AiToolInputValidator { } if (file.getSize() > MAX_INPUT_FILE_BYTES) { throw new ResponseStatusException( - HttpStatus.PAYLOAD_TOO_LARGE, + HttpStatus.CONTENT_TOO_LARGE, "PDF exceeds maximum size of " + (MAX_INPUT_FILE_BYTES / (1024 * 1024)) + " MB for AI tools"); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/AiWorkflowService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/AiWorkflowService.java index 74e68d4602..b9e3488324 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/AiWorkflowService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/AiWorkflowService.java @@ -774,7 +774,7 @@ public class AiWorkflowService { String[] errorHolder) { try { JsonNode node = objectMapper.readTree(line); - String event = node.path("event").asText(); + String event = node.path("event").asString(); switch (event) { case "progress" -> { AiEngineProgressDetail detail = @@ -785,7 +785,7 @@ public class AiWorkflowService { JsonNode response = node.path("response"); resultHolder[0] = objectMapper.treeToValue(response, AiWorkflowResponse.class); } - case "error" -> errorHolder[0] = node.path("message").asText("unknown error"); + case "error" -> errorHolder[0] = node.path("message").asString("unknown error"); case "heartbeat" -> listener.onHeartbeat(); default -> log.warn("Ignoring unknown engine stream event: {}", event); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/storage/service/FileStorageService.java b/app/proprietary/src/main/java/stirling/software/proprietary/storage/service/FileStorageService.java index 37d1e5fa43..32371222f6 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/storage/service/FileStorageService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/storage/service/FileStorageService.java @@ -943,7 +943,7 @@ public class FileStorageService { long maxFileBytes = toBytes(quotas.getMaxFileMb()); if (maxFileBytes > 0 && newBytes > maxFileBytes) { throw new ResponseStatusException( - HttpStatus.PAYLOAD_TOO_LARGE, "Stored file exceeds the maximum size"); + HttpStatus.CONTENT_TOO_LARGE, "Stored file exceeds the maximum size"); } long delta = newBytes - existingBytes; @@ -956,7 +956,7 @@ public class FileStorageService { long currentBytes = storedFileRepository.sumStorageBytesByOwner(owner); if (currentBytes + delta > maxUserBytes) { throw new ResponseStatusException( - HttpStatus.PAYLOAD_TOO_LARGE, "User storage quota exceeded"); + HttpStatus.CONTENT_TOO_LARGE, "User storage quota exceeded"); } } @@ -965,7 +965,7 @@ public class FileStorageService { long totalBytes = storedFileRepository.sumStorageBytesTotal(); if (totalBytes + delta > maxTotalBytes) { throw new ResponseStatusException( - HttpStatus.PAYLOAD_TOO_LARGE, "System storage quota exceeded"); + HttpStatus.CONTENT_TOO_LARGE, "System storage quota exceeded"); } } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/SigningFinalizationService.java b/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/SigningFinalizationService.java index 30673ab5bd..e5e122df45 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/SigningFinalizationService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/SigningFinalizationService.java @@ -1045,12 +1045,12 @@ public class SigningFinalizationService { if (certNode.has("p12Keystore")) { submission.setP12Keystore( metadataEncryptionService.decryptBytes( - certNode.get("p12Keystore").asText())); + certNode.get("p12Keystore").asString())); } if (certNode.has("jksKeystore")) { submission.setJksKeystore( metadataEncryptionService.decryptBytes( - certNode.get("jksKeystore").asText())); + certNode.get("jksKeystore").asString())); } return submission; } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/controller/api/ProprietaryUIDataControllerMoreTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/controller/api/ProprietaryUIDataControllerMoreTest.java index 58b8972a45..6b469f7ba2 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/controller/api/ProprietaryUIDataControllerMoreTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/controller/api/ProprietaryUIDataControllerMoreTest.java @@ -235,7 +235,7 @@ class ProprietaryUIDataControllerMoreTest { CustomSaml2AuthenticatedPrincipal principal = new CustomSaml2AuthenticatedPrincipal( - "samluser", Map.of(), "nameId", List.of()); + "samluser", Map.of(), "nameId", List.of(), "response"); Authentication auth = new UsernamePasswordAuthenticationToken(principal, null, List.of()); diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/AiToolInputValidatorTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/AiToolInputValidatorTest.java index 5f8c906fce..2c4a1fe815 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/service/AiToolInputValidatorTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/AiToolInputValidatorTest.java @@ -75,6 +75,6 @@ class AiToolInputValidatorTest { assertThrows( ResponseStatusException.class, () -> AiToolInputValidator.validatePdfUpload(file)); - assertEquals(HttpStatus.PAYLOAD_TOO_LARGE, ex.getStatusCode()); + assertEquals(HttpStatus.CONTENT_TOO_LARGE, ex.getStatusCode()); } }