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.

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## 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.
This commit is contained in:
brios
2026-07-21 18:49:59 +00:00
committed by GitHub
parent e2ea720fc8
commit e24a30828b
12 changed files with 83 additions and 47 deletions
@@ -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<String> 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());
@@ -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<String, Object> providedParams) {
@@ -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);
@@ -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");
@@ -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<String, List<Object>> attributes,
String nameId,
List<String> sessionIndexes)
implements Saml2AuthenticatedPrincipal, Serializable {
List<String> sessionIndexes,
String responseValue)
implements Saml2ResponseAssertionAccessor, AuthenticatedPrincipal, Serializable {
@Override
public String getName() {
@@ -24,4 +26,33 @@ public record CustomSaml2AuthenticatedPrincipal(
public Map<String, List<Object>> getAttributes() {
return this.attributes;
}
@Override
public String getNameId() {
return this.nameId;
}
@Override
public List<String> getSessionIndexes() {
return this.sessionIndexes;
}
@Override
public String getResponseValue() {
return this.responseValue;
}
@Override
@SuppressWarnings("unchecked")
public <A> List<A> getAttribute(String name) {
List<Object> values = this.attributes.get(name);
return values != null ? (List<A>) values : null;
}
@Override
@SuppressWarnings("unchecked")
public <A> A getFirstAttribute(String name) {
List<Object> values = this.attributes.get(name);
return values != null && !values.isEmpty() ? (A) values.get(0) : null;
}
}
@@ -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,
@@ -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");
@@ -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);
}
@@ -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");
}
}
}
@@ -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;
}
@@ -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());
@@ -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());
}
}