refactor(api): standardize syntax and simplify type declarations across security, workflow, and controller modules (#7127)

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
brios
2026-08-29 23:19:26 +01:00
committed by GitHub
co-authored by Anthony Stirling
parent 0b7b4e02c2
commit 34694c6f5e
35 changed files with 229 additions and 194 deletions
@@ -237,7 +237,7 @@ public class TabulaTableParser implements TableParser {
score -= 0.3f;
}
return Math.max(0f, Math.min(1f, score));
return Math.clamp(score, 0f, 1f);
}
private Bounds tableBounds(Table table) {
@@ -15,7 +15,8 @@ public class StringToMapPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
TypeReference<HashMap<String, String>> typeRef = new TypeReference<>() {};
TypeReference<HashMap<String, String>> typeRef =
new TypeReference<HashMap<String, String>>() {};
Map<String, String> map = objectMapper.readValue(text, typeRef);
setValue(map);
} catch (Exception e) {
@@ -237,7 +237,7 @@ public class EditTextController {
Matcher matcher = edit.pattern().matcher(joined);
List<MatchSpan> spans = new ArrayList<>();
StringBuffer interpolation = new StringBuffer();
StringBuilder interpolation = new StringBuilder();
int previousAppendPosition = 0;
while (matcher.find()) {
if (matcher.start() == matcher.end()) {
@@ -95,7 +95,8 @@ public class UIDataController {
try (InputStream is = resource.getInputStream()) {
Map<String, List<Dependency>> licenseData =
objectMapper.readValue(is, new TypeReference<>() {});
objectMapper.readValue(
is, new TypeReference<Map<String, List<Dependency>>>() {});
data.setDependencies(licenseData.get("dependencies"));
} catch (IOException e) {
log.error("Failed to load licenses data", e);
@@ -25,12 +25,15 @@ final class FormPayloadParser {
private static final String KEY_VALUE = "value";
private static final String KEY_DEFAULT_VALUE = "defaultValue";
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {};
private static final TypeReference<Map<String, Object>> MAP_TYPE =
new TypeReference<Map<String, Object>>() {};
private static final TypeReference<List<FormUtils.ModifyFormFieldDefinition>>
MODIFY_FIELD_LIST_TYPE = new TypeReference<>() {};
MODIFY_FIELD_LIST_TYPE =
new TypeReference<List<FormUtils.ModifyFormFieldDefinition>>() {};
private static final TypeReference<List<FormUtils.NewFormFieldDefinition>> NEW_FIELD_LIST_TYPE =
new TypeReference<>() {};
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {};
private static final TypeReference<List<String>> STRING_LIST_TYPE =
new TypeReference<List<String>>() {};
private FormPayloadParser() {}
@@ -96,7 +96,9 @@ public class AddCommentsController {
List<CommentSpecDto> dtos;
try {
dtos = objectMapper.readValue(commentsJson, new TypeReference<>() {});
dtos =
objectMapper.readValue(
commentsJson, new TypeReference<List<CommentSpecDto>>() {});
} catch (JacksonException e) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "comments must be a JSON array of CommentSpec objects");
@@ -59,7 +59,7 @@ public enum AuditLevel {
*/
public static AuditLevel fromInt(int level) {
// Ensure level is within valid bounds
int boundedLevel = Math.min(Math.max(level, 0), 3);
int boundedLevel = Math.clamp(level, 0, 3);
for (AuditLevel auditLevel : values()) {
if (auditLevel.level == boundedLevel) {
@@ -44,8 +44,10 @@ public class ValkeyJobStore implements JobStore {
private static final String FILE_INDEX_PREFIX = "stirling:file2job:";
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final TypeReference<List<String>> LIST_STRING = new TypeReference<>() {};
private static final TypeReference<Map<String, String>> MAP_STRING = new TypeReference<>() {};
private static final TypeReference<List<String>> LIST_STRING =
new TypeReference<List<String>>() {};
private static final TypeReference<Map<String, String>> MAP_STRING =
new TypeReference<Map<String, String>>() {};
private final StringRedisTemplate template;
@@ -35,7 +35,7 @@ public class AuditConfigurationProperties {
// Ensure level is within valid bounds (0-3)
int configLevel = auditConfig.getLevel();
this.level = Math.min(Math.max(configLevel, 0), 3);
this.level = Math.clamp(configLevel, 0, 3);
// Retention days (0 means infinite)
this.retentionDays = auditConfig.getRetentionDays();
@@ -48,7 +48,7 @@ public class UsageRestController {
@RequestParam(value = "dataType", defaultValue = "all") String dataType,
@RequestParam(value = "days", defaultValue = "30") Integer days) {
int lookbackDays = Math.max(1, Math.min(days, 365));
int lookbackDays = Math.clamp(days, 1, 365);
// Get audit events filtered by type
List<PersistentAuditEvent> events = getEventsByDataType(dataType, lookbackDays);
@@ -1,5 +1,6 @@
package stirling.software.proprietary.model;
import java.io.Serial;
import java.io.Serializable;
import jakarta.persistence.*;
@@ -19,7 +20,7 @@ import lombok.*;
@ToString
public class UserLicenseSettings implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
public static final Long SINGLETON_ID = 1L;
@@ -70,21 +70,23 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
if (!response.isCommitted()) {
if (authentication != null) {
if (authentication instanceof Saml2Authentication samlAuthentication) {
// Handle SAML2 logout redirection
getRedirect_saml2(request, response, samlAuthentication);
} else if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {
// Handle OAuth2 logout redirection
getRedirect_oauth2(request, response, oAuthToken);
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
// Handle Username/Password logout
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
} else {
// Handle unknown authentication types
log.error(
"Authentication class unknown: {}",
authentication.getClass().getSimpleName());
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
switch (authentication) {
case Saml2Authentication samlAuthentication ->
// Handle SAML2 logout redirection
getRedirect_saml2(request, response, samlAuthentication);
case OAuth2AuthenticationToken oAuthToken ->
// Handle OAuth2 logout redirection
getRedirect_oauth2(request, response, oAuthToken);
case UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken ->
// Handle Username/Password logout
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
default -> {
// Handle unknown authentication types
log.error(
"Authentication class unknown: {}",
authentication.getClass().getSimpleName());
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
}
}
} else {
if (jwtService != null) {
@@ -392,40 +392,40 @@ public class SecurityConfiguration {
// Handle OAUTH2 Logins
if (securityProperties.isOauth2Active()) {
http.oauth2Login(
oauth2 -> {
oauth2.loginPage("/login")
.authorizationEndpoint(
authorizationEndpoint -> {
if (clientRegistrationRepository != null) {
authorizationEndpoint
.authorizationRequestResolver(
new TauriAuthorizationRequestResolver(
clientRegistrationRepository));
}
})
.successHandler(
new CustomOAuth2AuthenticationSuccessHandler(
loginAttemptService,
securityProperties.getOauth2(),
userService,
jwtService,
licenseSettingsService,
applicationProperties))
.failureHandler(new CustomOAuth2AuthenticationFailureHandler())
// Add existing Authorities from the database
.userInfoEndpoint(
userInfoEndpoint ->
userInfoEndpoint
.oidcUserService(
new CustomOAuth2UserService(
securityProperties
.getOauth2(),
userService,
loginAttemptService))
.userAuthoritiesMapper(
oAuth2userAuthoritiesMapper))
.permitAll();
});
oauth2 ->
oauth2.loginPage("/login")
.authorizationEndpoint(
authorizationEndpoint -> {
if (clientRegistrationRepository != null) {
authorizationEndpoint
.authorizationRequestResolver(
new TauriAuthorizationRequestResolver(
clientRegistrationRepository));
}
})
.successHandler(
new CustomOAuth2AuthenticationSuccessHandler(
loginAttemptService,
securityProperties.getOauth2(),
userService,
jwtService,
licenseSettingsService,
applicationProperties))
.failureHandler(
new CustomOAuth2AuthenticationFailureHandler())
// Add existing Authorities from the database
.userInfoEndpoint(
userInfoEndpoint ->
userInfoEndpoint
.oidcUserService(
new CustomOAuth2UserService(
securityProperties
.getOauth2(),
userService,
loginAttemptService))
.userAuthoritiesMapper(
oAuth2userAuthoritiesMapper))
.permitAll());
}
// Handle SAML
if (securityProperties.isSaml2Active() && runningProOrHigher) {
@@ -703,17 +703,18 @@ public class AuthController {
}
private long extractEpochMillis(Object claimValue) {
if (claimValue == null) {
return -1L;
}
if (claimValue instanceof java.util.Date date) {
return date.getTime();
}
if (claimValue instanceof Number number) {
long epochSeconds = number.longValue();
return epochSeconds * 1000L;
switch (claimValue) {
case null -> {
return -1L;
}
case java.util.Date date -> {
return date.getTime();
}
case Number number -> {
long epochSeconds = number.longValue();
return epochSeconds * 1000L;
}
default -> {}
}
return -1L;
@@ -760,14 +760,14 @@ public class UserController {
for (Object principal : principals) {
List<SessionInformation> sessionsInformation =
sessionRegistry.getAllSessions(principal, false);
if (principal instanceof UserDetails detailsUser) {
userNameP = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
userNameP = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
userNameP = saml2User.name();
} else if (principal instanceof String stringUser) {
userNameP = stringUser;
switch (principal) {
case null -> {}
case UserDetails detailsUser -> userNameP = detailsUser.getUsername();
case OAuth2User oAuth2User -> userNameP = oAuth2User.getName();
case CustomSaml2AuthenticatedPrincipal saml2User ->
userNameP = saml2User.name();
case String stringUser -> userNameP = stringUser;
default -> {}
}
if (userNameP.equalsIgnoreCase(username)) {
for (SessionInformation sessionInfo : sessionsInformation) {
@@ -1,5 +1,6 @@
package stirling.software.proprietary.security.model;
import java.io.Serial;
import java.io.Serializable;
import org.springframework.security.core.GrantedAuthority;
@@ -28,7 +29,7 @@ import lombok.Setter;
@Setter
public class Authority implements GrantedAuthority, Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.security.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -18,7 +19,7 @@ import lombok.Setter;
@Setter
public class InviteToken implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -36,57 +36,62 @@ public class CustomOAuth2AuthenticationFailureHandler
AuthenticationException exception)
throws IOException, ServletException {
if (exception instanceof BadCredentialsException) {
log.error("BadCredentialsException", exception);
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
return;
}
if (exception instanceof DisabledException) {
log.error("User is deactivated: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
return;
}
if (exception instanceof LockedException) {
log.error("Account locked: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
return;
}
if (exception instanceof OAuth2AuthenticationException oAuth2Exception) {
OAuth2Error error = oAuth2Exception.getError();
String errorCode = error.getErrorCode();
if ("Password must not be null".equals(error.getErrorCode())) {
errorCode = "userAlreadyExistsWeb";
switch (exception) {
case BadCredentialsException badCredentialsException -> {
log.error("BadCredentialsException", exception);
getRedirectStrategy()
.sendRedirect(request, response, "/login?error=badCredentials");
return;
}
case DisabledException disabledException -> {
log.error("User is deactivated: ", exception);
getRedirectStrategy()
.sendRedirect(request, response, "/logout?userIsDisabled=true");
return;
}
case LockedException lockedException -> {
log.error("Account locked: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
return;
}
case OAuth2AuthenticationException oAuth2Exception -> {
OAuth2Error error = oAuth2Exception.getError();
log.error(
"OAuth2 Authentication error: {}",
errorCode != null ? errorCode : exception.getMessage(),
exception);
String errorValue = errorCode != null ? errorCode : "oauth2AuthenticationError";
clearRedirectCookie(response);
boolean tauriState = TauriOAuthUtils.isTauriState(request);
String redirectUrl;
if (tauriState) {
String basePath =
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
redirectUrl = basePath;
String stateParam = request.getParameter("state");
if (stateParam != null && !stateParam.isBlank()) {
redirectUrl = appendQueryParam(redirectUrl, "state", stateParam);
// Extract and pass nonce for CSRF validation
String nonce = TauriOAuthUtils.extractNonceFromState(stateParam);
if (nonce != null) {
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
}
String errorCode = error.getErrorCode();
if ("Password must not be null".equals(error.getErrorCode())) {
errorCode = "userAlreadyExistsWeb";
}
redirectUrl = appendQueryParam(redirectUrl, "errorOAuth", errorValue);
} else {
redirectUrl = buildFailureRedirectUrl(request, errorValue);
log.error(
"OAuth2 Authentication error: {}",
errorCode != null ? errorCode : exception.getMessage(),
exception);
String errorValue = errorCode != null ? errorCode : "oauth2AuthenticationError";
clearRedirectCookie(response);
boolean tauriState = TauriOAuthUtils.isTauriState(request);
String redirectUrl;
if (tauriState) {
String basePath =
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
redirectUrl = basePath;
String stateParam = request.getParameter("state");
if (stateParam != null && !stateParam.isBlank()) {
redirectUrl = appendQueryParam(redirectUrl, "state", stateParam);
// Extract and pass nonce for CSRF validation
String nonce = TauriOAuthUtils.extractNonceFromState(stateParam);
if (nonce != null) {
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
}
}
redirectUrl = appendQueryParam(redirectUrl, "errorOAuth", errorValue);
} else {
redirectUrl = buildFailureRedirectUrl(request, errorValue);
}
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
return;
}
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
return;
default -> {}
}
log.error("Unhandled authentication exception", exception);
super.onAuthenticationFailure(request, response, exception);
@@ -61,7 +61,12 @@ public class CustomSaml2ResponseAuthenticationConverter
@Override
public Saml2Authentication convert(ResponseToken responseToken) {
Assertion assertion = responseToken.getResponse().getAssertions().getFirst();
List<Assertion> assertions = responseToken.getResponse().getAssertions();
if (assertions == null || assertions.isEmpty()) {
log.error("SAML response contains no assertions");
return null;
}
Assertion assertion = assertions.getFirst();
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Debug log with actual values
@@ -213,8 +213,11 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
}
sb.append(
"\nWARNING: this block contains PII. Set security.oauth2.debugLogging=false once"
+ " troubleshooting is complete.\n");
"""
WARNING: this block contains PII. Set security.oauth2.debugLogging=false once\
troubleshooting is complete.
""");
sb.append("========== [/OAUTH2 DEBUG] ==========");
if (failure) {
@@ -132,7 +132,9 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
verifyingKeyCache.put(
key.getKeyId(), new JwtVerificationKey(key.getKeyId(), key.getVerifyingKey()));
}
activeKey = new JwtVerificationKey(keys.get(0).getKeyId(), keys.get(0).getVerifyingKey());
activeKey =
new JwtVerificationKey(
keys.getFirst().getKeyId(), keys.getFirst().getVerifyingKey());
log.info("Loaded {} JWT key(s) from DB, active key: {}", keys.size(), activeKey.getKeyId());
}
@@ -640,14 +640,14 @@ public class UserService implements UserServiceInterface {
for (Object principal : sessionRegistry.getAllPrincipals()) {
for (SessionInformation sessionsInformation :
sessionRegistry.getAllSessions(principal, false)) {
if (principal instanceof UserDetails detailsUser) {
usernameP = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
usernameP = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
usernameP = saml2User.name();
} else if (principal instanceof String stringUser) {
usernameP = stringUser;
switch (principal) {
case null -> {}
case UserDetails detailsUser -> usernameP = detailsUser.getUsername();
case OAuth2User oAuth2User -> usernameP = oAuth2User.getName();
case CustomSaml2AuthenticatedPrincipal saml2User ->
usernameP = saml2User.name();
case String stringUser -> usernameP = stringUser;
default -> {}
}
if (usernameP.equalsIgnoreCase(username)) {
sessionRegistry.expireSession(sessionsInformation.getSessionId());
@@ -47,14 +47,13 @@ public class SessionPersistentRegistry implements SessionRegistry {
List<SessionInformation> sessionInformations = new ArrayList<>();
String principalName = null;
if (principal instanceof UserDetails detailsUser) {
principalName = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
principalName = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
principalName = saml2User.name();
} else if (principal instanceof String stringUser) {
principalName = stringUser;
switch (principal) {
case null -> {}
case UserDetails detailsUser -> principalName = detailsUser.getUsername();
case OAuth2User oAuth2User -> principalName = oAuth2User.getName();
case CustomSaml2AuthenticatedPrincipal saml2User -> principalName = saml2User.name();
case String stringUser -> principalName = stringUser;
default -> {}
}
if (principalName != null) {
@@ -78,14 +77,13 @@ public class SessionPersistentRegistry implements SessionRegistry {
public void registerNewSession(String sessionId, Object principal) {
String principalName = null;
if (principal instanceof UserDetails detailsUser) {
principalName = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
principalName = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
principalName = saml2User.name();
} else if (principal instanceof String stringUser) {
principalName = stringUser;
switch (principal) {
case null -> {}
case UserDetails detailsUser -> principalName = detailsUser.getUsername();
case OAuth2User oAuth2User -> principalName = oAuth2User.getName();
case CustomSaml2AuthenticatedPrincipal saml2User -> principalName = saml2User.name();
case String stringUser -> principalName = stringUser;
default -> {}
}
if (principalName != null) {
@@ -1,5 +1,6 @@
package stirling.software.proprietary.storage.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -46,7 +47,7 @@ import stirling.software.proprietary.security.model.User;
@Setter
public class FileShare implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.storage.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -39,7 +40,7 @@ import stirling.software.proprietary.security.model.User;
@Setter
public class FileShareAccess implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.storage.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -24,7 +25,7 @@ import lombok.Setter;
@Setter
public class StorageCleanupEntry implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.storage.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.HashSet;
@@ -45,7 +46,7 @@ import stirling.software.proprietary.workflow.model.WorkflowSession;
@Setter
public class StoredFile implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.storage.model;
import java.io.Serial;
import java.io.Serializable;
import jakarta.persistence.Column;
@@ -19,7 +20,7 @@ import lombok.Setter;
@Setter
public class StoredFileBlob implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@Column(name = "storage_key", nullable = false, length = 128)
@@ -7,6 +7,7 @@ import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -64,7 +65,7 @@ public class AuditWebFilter extends OncePerRequestFilter {
if (auth != null && auth.getAuthorities() != null) {
String roles =
auth.getAuthorities().stream()
.map(a -> a.getAuthority())
.map(GrantedAuthority::getAuthority)
.reduce((a, b) -> a + "," + b)
.orElse("");
MDC.put("userRoles", roles);
@@ -37,6 +37,7 @@ import stirling.software.proprietary.workflow.dto.CertificateInfo;
import stirling.software.proprietary.workflow.dto.CertificateValidationResponse;
import stirling.software.proprietary.workflow.dto.ParticipantRequest;
import stirling.software.proprietary.workflow.dto.WorkflowCreationRequest;
import stirling.software.proprietary.workflow.model.WorkflowParticipant;
import stirling.software.proprietary.workflow.model.WorkflowSession;
import stirling.software.proprietary.workflow.service.CertificateSubmissionValidator;
import stirling.software.proprietary.workflow.service.SigningFinalizationService;
@@ -259,7 +260,9 @@ public class SigningSessionController {
+ "database until manual cleanup.",
sessionId,
session.getParticipants() != null
? session.getParticipants().stream().map(p -> p.getEmail()).toList()
? session.getParticipants().stream()
.map(WorkflowParticipant::getEmail)
.toList()
: "unknown",
e);
throw new ResponseStatusException(
@@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.ContentDisposition;
@@ -429,7 +430,7 @@ public class WorkflowParticipantController {
java.util.List<Map<String, Object>> wetSigs =
objectMapper.readValue(
request.getWetSignaturesData(),
new TypeReference<java.util.List<Map<String, Object>>>() {});
new TypeReference<List<Map<String, Object>>>() {});
if (wetSigs.size() > WetSignatureMetadata.MAX_SIGNATURES_PER_PARTICIPANT) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Too many wet signatures submitted");
@@ -1,5 +1,6 @@
package stirling.software.proprietary.workflow.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -51,7 +52,7 @@ import stirling.software.proprietary.storage.model.ShareAccessRole;
@Setter
public class WorkflowParticipant implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,5 +1,6 @@
package stirling.software.proprietary.workflow.model;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -53,7 +54,7 @@ import stirling.software.proprietary.storage.model.StoredFile;
@Setter
public class WorkflowSession implements Serializable {
private static final long serialVersionUID = 1L;
@Serial private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -217,16 +217,13 @@ public class SigningFinalizationService {
wetSignatures.size(),
session.getSessionId());
PDDocument document = pdfDocumentFactory.load(new ByteArrayInputStream(pdfBytes));
try {
try (PDDocument document = pdfDocumentFactory.load(new ByteArrayInputStream(pdfBytes))) {
for (WetSignatureMetadata wetSig : wetSignatures) {
applyWetSignatureToPage(document, wetSig);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
return baos.toByteArray();
} finally {
document.close();
}
}
@@ -242,11 +239,10 @@ public class SigningFinalizationService {
}
PDPage page = document.getPage(pageIndex);
PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
try {
try (PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
// Use WetSignatureMetadata.extractBase64Data() to strip data URL prefix
String base64Data = wetSig.extractBase64Data();
if (base64Data == null || base64Data.isBlank()) {
@@ -279,8 +275,6 @@ public class SigningFinalizationService {
pdfY,
width,
height);
} finally {
contentStream.close();
}
}
@@ -954,21 +954,22 @@ public class WorkflowSessionService {
Object pemObject = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PrivateKeyInfo keyInfo;
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo encrypted) {
InputDecryptorProvider decryptor =
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password);
keyInfo = encrypted.decryptPrivateKeyInfo(decryptor);
} else if (pemObject instanceof PEMEncryptedKeyPair encryptedKeyPair) {
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(password);
keyInfo = encryptedKeyPair.decryptKeyPair(decryptor).getPrivateKeyInfo();
} else if (pemObject instanceof PEMKeyPair keyPair) {
keyInfo = keyPair.getPrivateKeyInfo();
} else if (pemObject instanceof PrivateKeyInfo info) {
keyInfo = info;
} else {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Unsupported PEM private key format");
switch (pemObject) {
case PKCS8EncryptedPrivateKeyInfo encrypted -> {
InputDecryptorProvider decryptor =
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password);
keyInfo = encrypted.decryptPrivateKeyInfo(decryptor);
}
case PEMEncryptedKeyPair encryptedKeyPair -> {
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(password);
keyInfo = encryptedKeyPair.decryptKeyPair(decryptor).getPrivateKeyInfo();
}
case PEMKeyPair keyPair -> keyInfo = keyPair.getPrivateKeyInfo();
case PrivateKeyInfo info -> keyInfo = info;
case null, default ->
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Unsupported PEM private key format");
}
return converter.getPrivateKey(keyInfo);
}