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