feat: Add RegexPatternUtils for centralized regex management, file naming funcs, UtilityClass annotation (#4218)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
Balázs Szücs
2025-09-28 17:56:35 +02:00
committed by GitHub
parent 133e6d3de6
commit 045f4cc591
78 changed files with 1947 additions and 617 deletions

View File

@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.RequestUriUtils;
import stirling.software.proprietary.config.AuditConfigurationProperties;
@@ -323,7 +324,10 @@ public class AuditUtils {
return AuditEventType.SETTINGS_CHANGED;
} else if (cls.contains("file")
|| path.startsWith("/file")
|| path.matches("(?i).*/(upload|download)/.*")) {
|| RegexPatternUtils.getInstance()
.getUploadDownloadPathPattern()
.matcher(path)
.matches()) {
return AuditEventType.FILE_OPERATION;
}
}
@@ -387,7 +391,10 @@ public class AuditUtils {
return AuditEventType.SETTINGS_CHANGED;
} else if (cls.contains("file")
|| path.startsWith("/file")
|| path.matches("(?i).*/(upload|download)/.*")) {
|| RegexPatternUtils.getInstance()
.getUploadDownloadPathPattern()
.matcher(path)
.matches()) {
return AuditEventType.FILE_OPERATION;
} else {
return AuditEventType.PDF_PROCESS;

View File

@@ -27,6 +27,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
import stirling.software.common.model.oauth2.KeycloakProvider;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.UrlUtils;
import stirling.software.proprietary.audit.AuditEventType;
import stirling.software.proprietary.audit.AuditLevel;
@@ -250,6 +251,9 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
* @return a sanitised <code>String</code>
*/
private String sanitizeInput(String input) {
return input.replaceAll("[^a-zA-Z0-9 ]", "");
return RegexPatternUtils.getInstance()
.getInputSanitizePattern()
.matcher(input)
.replaceAll("");
}
}

View File

@@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.RegexPatternUtils;
@Service
@Slf4j
@@ -117,7 +118,11 @@ public class KeygenLicenseVerifier {
// Remove the footer
encodedPayload = encodedPayload.replace(CERT_SUFFIX, "");
// Remove all newlines
encodedPayload = encodedPayload.replaceAll("\\r?\\n", "");
encodedPayload =
RegexPatternUtils.getInstance()
.getEncodedPayloadNewlinePattern()
.matcher(encodedPayload)
.replaceAll("");
byte[] payloadBytes = Base64.getDecoder().decode(encodedPayload);
String payload = new String(payloadBytes);

View File

@@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.proprietary.security.model.api.admin.SettingValueResponse;
import stirling.software.proprietary.security.model.api.admin.UpdateSettingValueRequest;
import stirling.software.proprietary.security.model.api.admin.UpdateSettingsRequest;
@@ -444,7 +445,8 @@ public class AdminSettingsController {
"legal");
// Pattern to validate safe property paths - only alphanumeric, dots, and underscores
private static final Pattern SAFE_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9._]+$");
private static final Pattern SAFE_KEY_PATTERN =
RegexPatternUtils.getInstance().getPattern("^[a-zA-Z0-9._]+$");
private static final int MAX_NESTING_DEPTH = 10;
// Security: Generic error messages to prevent information disclosure

View File

@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.util.RegexPatternUtils;
@Component
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
@@ -143,6 +144,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
}
private static String stripNewlines(final String s) {
return s.replaceAll("[\n\r]", "");
return RegexPatternUtils.getInstance().getNewlineCharsPattern().matcher(s).replaceAll("");
}
}

View File

@@ -75,8 +75,11 @@ public class CustomUserDetailsService implements UserDetailsService {
*/
private AuthenticationType determinePreferredSSOType() {
// Check what SSO types are enabled and prefer in order: OAUTH2 > SAML2 > fallback to OAUTH2
boolean oauth2Enabled = securityProperties.getOauth2() != null && securityProperties.getOauth2().getEnabled();
boolean saml2Enabled = securityProperties.getSaml2() != null && securityProperties.getSaml2().getEnabled();
boolean oauth2Enabled =
securityProperties.getOauth2() != null
&& securityProperties.getOauth2().getEnabled();
boolean saml2Enabled =
securityProperties.getSaml2() != null && securityProperties.getSaml2().getEnabled();
if (oauth2Enabled) {
return AuthenticationType.OAUTH2;

View File

@@ -31,6 +31,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.common.service.UserServiceInterface;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.database.repository.AuthorityRepository;
import stirling.software.proprietary.security.database.repository.UserRepository;
@@ -480,13 +481,18 @@ public class UserService implements UserServiceInterface {
// Checks whether the simple username is formatted correctly
// Regular expression for user name: Min. 3 characters, max. 50 characters
boolean isValidSimpleUsername =
username.matches("^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$");
RegexPatternUtils.getInstance()
.getUsernameValidationPattern()
.matcher(username)
.matches();
// Checks whether the email address is formatted correctly
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
boolean isValidEmail =
username.matches(
"^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\\\\.[A-Za-z0-9-]+)*(?:\\\\.[A-Za-z]{2,})$");
RegexPatternUtils.getInstance()
.getEmailValidationPattern()
.matcher(username)
.matches();
List<String> notAllowedUserList = new ArrayList<>();
notAllowedUserList.add("ALL_USERS".toLowerCase());

View File

@@ -7,13 +7,16 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.RegexPatternUtils;
/** Redacts any map values whose keys match common secret/token patterns. */
@Slf4j
public final class SecretMasker {
private static final Pattern SENSITIVE =
Pattern.compile(
"(?i)(password|token|secret|api[_-]?key|authorization|auth|jwt|cred|cert)");
RegexPatternUtils.getInstance()
.getPattern(
"(?i)(password|token|secret|api[_-]?key|authorization|auth|jwt|cred|cert)");
private SecretMasker() {}