This commit is contained in:
Anthony Stirling
2025-10-24 19:27:37 +01:00
parent 928a591839
commit e4db9e183d
26 changed files with 1528 additions and 867 deletions

View File

@@ -74,8 +74,7 @@ public class AppConfig {
@Bean(name = "appName")
public String appName() {
String homeTitle = applicationProperties.getUi().getAppName();
return (homeTitle != null) ? homeTitle : "Stirling PDF";
return "Stirling PDF";
}
@Bean(name = "appVersion")
@@ -93,9 +92,7 @@ public class AppConfig {
@Bean(name = "homeText")
public String homeText() {
return (applicationProperties.getUi().getHomeDescription() != null)
? applicationProperties.getUi().getHomeDescription()
: "null";
return "null";
}
@Bean(name = "languages")
@@ -110,11 +107,8 @@ public class AppConfig {
@Bean(name = "navBarText")
public String navBarText() {
String defaultNavBar =
applicationProperties.getUi().getAppNameNavbar() != null
? applicationProperties.getUi().getAppNameNavbar()
: applicationProperties.getUi().getAppName();
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
String navBar = applicationProperties.getUi().getAppNameNavbar();
return (navBar != null) ? navBar : "Stirling PDF";
}
@Bean(name = "enableAlphaFunctionality")

View File

@@ -327,11 +327,6 @@ public class PostHogService {
applicationProperties.getSystem().isAnalyticsEnabled());
// Capture UI properties
addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName());
addIfNotEmpty(
properties,
"ui_homeDescription",
applicationProperties.getUi().getHomeDescription());
addIfNotEmpty(
properties, "ui_appNameNavbar", applicationProperties.getUi().getAppNameNavbar());

View File

@@ -22,6 +22,8 @@ public class AppArgsCapture implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
APP_ARGS.set(List.of(args.getSourceArgs()));
log.debug("Captured {} application arguments for restart capability", args.getSourceArgs().length);
log.debug(
"Captured {} application arguments for restart capability",
args.getSourceArgs().length);
}
}

View File

@@ -8,9 +8,7 @@ import java.nio.file.Paths;
import lombok.extern.slf4j.Slf4j;
/**
* Utility class to locate JAR files at runtime for restart operations
*/
/** Utility class to locate JAR files at runtime for restart operations */
@Slf4j
public class JarPathUtil {
@@ -45,8 +43,8 @@ public class JarPathUtil {
}
/**
* Gets the path to the restart-helper.jar file Expected to be in the same directory as the
* main JAR
* Gets the path to the restart-helper.jar file Expected to be in the same directory as the main
* JAR
*
* @return Path to restart-helper.jar, or null if not found
*/

View File

@@ -112,19 +112,11 @@ class ApplicationPropertiesLogicTest {
@Test
void ui_getters_return_null_for_blank() {
ApplicationProperties.Ui ui = new ApplicationProperties.Ui();
ui.setAppName(" ");
ui.setHomeDescription("");
ui.setAppNameNavbar(null);
assertNull(ui.getAppName());
assertNull(ui.getHomeDescription());
assertNull(ui.getAppNameNavbar());
ui.setAppName("Stirling-PDF");
ui.setHomeDescription("Home");
ui.setAppNameNavbar("Nav");
assertEquals("Stirling-PDF", ui.getAppName());
assertEquals("Home", ui.getHomeDescription());
assertEquals("Nav", ui.getAppNameNavbar());
}

View File

@@ -56,19 +56,22 @@ public class SettingsController {
public ResponseEntity<Map<String, Object>> getGeneralSettings() {
Map<String, Object> settings = new HashMap<>();
settings.put("ui", applicationProperties.getUi());
settings.put("system", Map.of(
"defaultLocale", applicationProperties.getSystem().getDefaultLocale(),
"showUpdate", applicationProperties.getSystem().isShowUpdate(),
"showUpdateOnlyAdmin", applicationProperties.getSystem().getShowUpdateOnlyAdmin(),
"customHTMLFiles", applicationProperties.getSystem().isCustomHTMLFiles(),
"fileUploadLimit", applicationProperties.getSystem().getFileUploadLimit()
));
settings.put(
"system",
Map.of(
"defaultLocale", applicationProperties.getSystem().getDefaultLocale(),
"showUpdate", applicationProperties.getSystem().isShowUpdate(),
"showUpdateOnlyAdmin",
applicationProperties.getSystem().getShowUpdateOnlyAdmin(),
"customHTMLFiles", applicationProperties.getSystem().isCustomHTMLFiles(),
"fileUploadLimit", applicationProperties.getSystem().getFileUploadLimit()));
return ResponseEntity.ok(settings);
}
@PostMapping("/admin/settings/general")
@Hidden
public ResponseEntity<String> updateGeneralSettings(@RequestBody Map<String, Object> settings) throws IOException {
public ResponseEntity<String> updateGeneralSettings(@RequestBody Map<String, Object> settings)
throws IOException {
// Update UI settings
if (settings.containsKey("ui")) {
Map<String, String> ui = (Map<String, String>) settings.get("ui");
@@ -83,23 +86,32 @@ public class SettingsController {
Map<String, Object> system = (Map<String, Object>) settings.get("system");
if (system.containsKey("defaultLocale")) {
GeneralUtils.saveKeyToSettings("system.defaultLocale", system.get("defaultLocale"));
applicationProperties.getSystem().setDefaultLocale((String) system.get("defaultLocale"));
applicationProperties
.getSystem()
.setDefaultLocale((String) system.get("defaultLocale"));
}
if (system.containsKey("showUpdate")) {
GeneralUtils.saveKeyToSettings("system.showUpdate", system.get("showUpdate"));
applicationProperties.getSystem().setShowUpdate((Boolean) system.get("showUpdate"));
}
if (system.containsKey("showUpdateOnlyAdmin")) {
GeneralUtils.saveKeyToSettings("system.showUpdateOnlyAdmin", system.get("showUpdateOnlyAdmin"));
applicationProperties.getSystem().setShowUpdateOnlyAdmin((Boolean) system.get("showUpdateOnlyAdmin"));
GeneralUtils.saveKeyToSettings(
"system.showUpdateOnlyAdmin", system.get("showUpdateOnlyAdmin"));
applicationProperties
.getSystem()
.setShowUpdateOnlyAdmin((Boolean) system.get("showUpdateOnlyAdmin"));
}
if (system.containsKey("fileUploadLimit")) {
GeneralUtils.saveKeyToSettings("system.fileUploadLimit", system.get("fileUploadLimit"));
applicationProperties.getSystem().setFileUploadLimit((String) system.get("fileUploadLimit"));
GeneralUtils.saveKeyToSettings(
"system.fileUploadLimit", system.get("fileUploadLimit"));
applicationProperties
.getSystem()
.setFileUploadLimit((String) system.get("fileUploadLimit"));
}
}
return ResponseEntity.ok("General settings updated. Restart required for changes to take effect.");
return ResponseEntity.ok(
"General settings updated. Restart required for changes to take effect.");
}
// ========== SECURITY SETTINGS ==========
@@ -115,61 +127,80 @@ public class SettingsController {
settings.put("loginMethod", security.getLoginMethod());
settings.put("loginAttemptCount", security.getLoginAttemptCount());
settings.put("loginResetTimeMinutes", security.getLoginResetTimeMinutes());
settings.put("initialLogin", Map.of(
"username", security.getInitialLogin().getUsername() != null ? security.getInitialLogin().getUsername() : ""
));
settings.put(
"initialLogin",
Map.of(
"username",
security.getInitialLogin().getUsername() != null
? security.getInitialLogin().getUsername()
: ""));
// JWT settings
ApplicationProperties.Security.Jwt jwt = security.getJwt();
settings.put("jwt", Map.of(
"enableKeystore", jwt.isEnableKeystore(),
"enableKeyRotation", jwt.isEnableKeyRotation(),
"enableKeyCleanup", jwt.isEnableKeyCleanup(),
"keyRetentionDays", jwt.getKeyRetentionDays(),
"secureCookie", jwt.isSecureCookie()
));
settings.put(
"jwt",
Map.of(
"enableKeystore", jwt.isEnableKeystore(),
"enableKeyRotation", jwt.isEnableKeyRotation(),
"enableKeyCleanup", jwt.isEnableKeyCleanup(),
"keyRetentionDays", jwt.getKeyRetentionDays()));
return ResponseEntity.ok(settings);
}
@PostMapping("/admin/settings/security")
@Hidden
public ResponseEntity<String> updateSecuritySettings(@RequestBody Map<String, Object> settings) throws IOException {
public ResponseEntity<String> updateSecuritySettings(@RequestBody Map<String, Object> settings)
throws IOException {
if (settings.containsKey("enableLogin")) {
GeneralUtils.saveKeyToSettings("security.enableLogin", settings.get("enableLogin"));
applicationProperties.getSecurity().setEnableLogin((Boolean) settings.get("enableLogin"));
applicationProperties
.getSecurity()
.setEnableLogin((Boolean) settings.get("enableLogin"));
}
if (settings.containsKey("csrfDisabled")) {
GeneralUtils.saveKeyToSettings("security.csrfDisabled", settings.get("csrfDisabled"));
applicationProperties.getSecurity().setCsrfDisabled((Boolean) settings.get("csrfDisabled"));
applicationProperties
.getSecurity()
.setCsrfDisabled((Boolean) settings.get("csrfDisabled"));
}
if (settings.containsKey("loginMethod")) {
GeneralUtils.saveKeyToSettings("security.loginMethod", settings.get("loginMethod"));
applicationProperties.getSecurity().setLoginMethod((String) settings.get("loginMethod"));
applicationProperties
.getSecurity()
.setLoginMethod((String) settings.get("loginMethod"));
}
if (settings.containsKey("loginAttemptCount")) {
GeneralUtils.saveKeyToSettings("security.loginAttemptCount", settings.get("loginAttemptCount"));
applicationProperties.getSecurity().setLoginAttemptCount((Integer) settings.get("loginAttemptCount"));
GeneralUtils.saveKeyToSettings(
"security.loginAttemptCount", settings.get("loginAttemptCount"));
applicationProperties
.getSecurity()
.setLoginAttemptCount((Integer) settings.get("loginAttemptCount"));
}
if (settings.containsKey("loginResetTimeMinutes")) {
GeneralUtils.saveKeyToSettings("security.loginResetTimeMinutes", settings.get("loginResetTimeMinutes"));
applicationProperties.getSecurity().setLoginResetTimeMinutes(((Number) settings.get("loginResetTimeMinutes")).longValue());
GeneralUtils.saveKeyToSettings(
"security.loginResetTimeMinutes", settings.get("loginResetTimeMinutes"));
applicationProperties
.getSecurity()
.setLoginResetTimeMinutes(
((Number) settings.get("loginResetTimeMinutes")).longValue());
}
// JWT settings
if (settings.containsKey("jwt")) {
Map<String, Object> jwt = (Map<String, Object>) settings.get("jwt");
if (jwt.containsKey("secureCookie")) {
GeneralUtils.saveKeyToSettings("security.jwt.secureCookie", jwt.get("secureCookie"));
applicationProperties.getSecurity().getJwt().setSecureCookie((Boolean) jwt.get("secureCookie"));
}
if (jwt.containsKey("keyRetentionDays")) {
GeneralUtils.saveKeyToSettings("security.jwt.keyRetentionDays", jwt.get("keyRetentionDays"));
applicationProperties.getSecurity().getJwt().setKeyRetentionDays((Integer) jwt.get("keyRetentionDays"));
GeneralUtils.saveKeyToSettings(
"security.jwt.keyRetentionDays", jwt.get("keyRetentionDays"));
applicationProperties
.getSecurity()
.getJwt()
.setKeyRetentionDays((Integer) jwt.get("keyRetentionDays"));
}
}
return ResponseEntity.ok("Security settings updated. Restart required for changes to take effect.");
return ResponseEntity.ok(
"Security settings updated. Restart required for changes to take effect.");
}
// ========== CONNECTIONS SETTINGS (OAuth/SAML) ==========
@@ -182,66 +213,100 @@ public class SettingsController {
// OAuth2 settings
ApplicationProperties.Security.OAUTH2 oauth2 = security.getOauth2();
settings.put("oauth2", Map.of(
"enabled", oauth2.getEnabled(),
"issuer", oauth2.getIssuer() != null ? oauth2.getIssuer() : "",
"clientId", oauth2.getClientId() != null ? oauth2.getClientId() : "",
"provider", oauth2.getProvider() != null ? oauth2.getProvider() : "",
"autoCreateUser", oauth2.getAutoCreateUser(),
"blockRegistration", oauth2.getBlockRegistration(),
"useAsUsername", oauth2.getUseAsUsername() != null ? oauth2.getUseAsUsername() : ""
));
settings.put(
"oauth2",
Map.of(
"enabled", oauth2.getEnabled(),
"issuer", oauth2.getIssuer() != null ? oauth2.getIssuer() : "",
"clientId", oauth2.getClientId() != null ? oauth2.getClientId() : "",
"provider", oauth2.getProvider() != null ? oauth2.getProvider() : "",
"autoCreateUser", oauth2.getAutoCreateUser(),
"blockRegistration", oauth2.getBlockRegistration(),
"useAsUsername",
oauth2.getUseAsUsername() != null
? oauth2.getUseAsUsername()
: ""));
// SAML2 settings
ApplicationProperties.Security.SAML2 saml2 = security.getSaml2();
settings.put("saml2", Map.of(
"enabled", saml2.getEnabled(),
"provider", saml2.getProvider() != null ? saml2.getProvider() : "",
"autoCreateUser", saml2.getAutoCreateUser(),
"blockRegistration", saml2.getBlockRegistration(),
"registrationId", saml2.getRegistrationId()
));
settings.put(
"saml2",
Map.of(
"enabled", saml2.getEnabled(),
"provider", saml2.getProvider() != null ? saml2.getProvider() : "",
"autoCreateUser", saml2.getAutoCreateUser(),
"blockRegistration", saml2.getBlockRegistration(),
"registrationId", saml2.getRegistrationId()));
return ResponseEntity.ok(settings);
}
@PostMapping("/admin/settings/connections")
@Hidden
public ResponseEntity<String> updateConnectionsSettings(@RequestBody Map<String, Object> settings) throws IOException {
public ResponseEntity<String> updateConnectionsSettings(
@RequestBody Map<String, Object> settings) throws IOException {
// OAuth2 settings
if (settings.containsKey("oauth2")) {
Map<String, Object> oauth2 = (Map<String, Object>) settings.get("oauth2");
if (oauth2.containsKey("enabled")) {
GeneralUtils.saveKeyToSettings("security.oauth2.enabled", oauth2.get("enabled"));
applicationProperties.getSecurity().getOauth2().setEnabled((Boolean) oauth2.get("enabled"));
applicationProperties
.getSecurity()
.getOauth2()
.setEnabled((Boolean) oauth2.get("enabled"));
}
if (oauth2.containsKey("issuer")) {
GeneralUtils.saveKeyToSettings("security.oauth2.issuer", oauth2.get("issuer"));
applicationProperties.getSecurity().getOauth2().setIssuer((String) oauth2.get("issuer"));
applicationProperties
.getSecurity()
.getOauth2()
.setIssuer((String) oauth2.get("issuer"));
}
if (oauth2.containsKey("clientId")) {
GeneralUtils.saveKeyToSettings("security.oauth2.clientId", oauth2.get("clientId"));
applicationProperties.getSecurity().getOauth2().setClientId((String) oauth2.get("clientId"));
applicationProperties
.getSecurity()
.getOauth2()
.setClientId((String) oauth2.get("clientId"));
}
if (oauth2.containsKey("clientSecret")) {
GeneralUtils.saveKeyToSettings("security.oauth2.clientSecret", oauth2.get("clientSecret"));
applicationProperties.getSecurity().getOauth2().setClientSecret((String) oauth2.get("clientSecret"));
GeneralUtils.saveKeyToSettings(
"security.oauth2.clientSecret", oauth2.get("clientSecret"));
applicationProperties
.getSecurity()
.getOauth2()
.setClientSecret((String) oauth2.get("clientSecret"));
}
if (oauth2.containsKey("provider")) {
GeneralUtils.saveKeyToSettings("security.oauth2.provider", oauth2.get("provider"));
applicationProperties.getSecurity().getOauth2().setProvider((String) oauth2.get("provider"));
applicationProperties
.getSecurity()
.getOauth2()
.setProvider((String) oauth2.get("provider"));
}
if (oauth2.containsKey("autoCreateUser")) {
GeneralUtils.saveKeyToSettings("security.oauth2.autoCreateUser", oauth2.get("autoCreateUser"));
applicationProperties.getSecurity().getOauth2().setAutoCreateUser((Boolean) oauth2.get("autoCreateUser"));
GeneralUtils.saveKeyToSettings(
"security.oauth2.autoCreateUser", oauth2.get("autoCreateUser"));
applicationProperties
.getSecurity()
.getOauth2()
.setAutoCreateUser((Boolean) oauth2.get("autoCreateUser"));
}
if (oauth2.containsKey("blockRegistration")) {
GeneralUtils.saveKeyToSettings("security.oauth2.blockRegistration", oauth2.get("blockRegistration"));
applicationProperties.getSecurity().getOauth2().setBlockRegistration((Boolean) oauth2.get("blockRegistration"));
GeneralUtils.saveKeyToSettings(
"security.oauth2.blockRegistration", oauth2.get("blockRegistration"));
applicationProperties
.getSecurity()
.getOauth2()
.setBlockRegistration((Boolean) oauth2.get("blockRegistration"));
}
if (oauth2.containsKey("useAsUsername")) {
GeneralUtils.saveKeyToSettings("security.oauth2.useAsUsername", oauth2.get("useAsUsername"));
applicationProperties.getSecurity().getOauth2().setUseAsUsername((String) oauth2.get("useAsUsername"));
GeneralUtils.saveKeyToSettings(
"security.oauth2.useAsUsername", oauth2.get("useAsUsername"));
applicationProperties
.getSecurity()
.getOauth2()
.setUseAsUsername((String) oauth2.get("useAsUsername"));
}
}
@@ -250,23 +315,38 @@ public class SettingsController {
Map<String, Object> saml2 = (Map<String, Object>) settings.get("saml2");
if (saml2.containsKey("enabled")) {
GeneralUtils.saveKeyToSettings("security.saml2.enabled", saml2.get("enabled"));
applicationProperties.getSecurity().getSaml2().setEnabled((Boolean) saml2.get("enabled"));
applicationProperties
.getSecurity()
.getSaml2()
.setEnabled((Boolean) saml2.get("enabled"));
}
if (saml2.containsKey("provider")) {
GeneralUtils.saveKeyToSettings("security.saml2.provider", saml2.get("provider"));
applicationProperties.getSecurity().getSaml2().setProvider((String) saml2.get("provider"));
applicationProperties
.getSecurity()
.getSaml2()
.setProvider((String) saml2.get("provider"));
}
if (saml2.containsKey("autoCreateUser")) {
GeneralUtils.saveKeyToSettings("security.saml2.autoCreateUser", saml2.get("autoCreateUser"));
applicationProperties.getSecurity().getSaml2().setAutoCreateUser((Boolean) saml2.get("autoCreateUser"));
GeneralUtils.saveKeyToSettings(
"security.saml2.autoCreateUser", saml2.get("autoCreateUser"));
applicationProperties
.getSecurity()
.getSaml2()
.setAutoCreateUser((Boolean) saml2.get("autoCreateUser"));
}
if (saml2.containsKey("blockRegistration")) {
GeneralUtils.saveKeyToSettings("security.saml2.blockRegistration", saml2.get("blockRegistration"));
applicationProperties.getSecurity().getSaml2().setBlockRegistration((Boolean) saml2.get("blockRegistration"));
GeneralUtils.saveKeyToSettings(
"security.saml2.blockRegistration", saml2.get("blockRegistration"));
applicationProperties
.getSecurity()
.getSaml2()
.setBlockRegistration((Boolean) saml2.get("blockRegistration"));
}
}
return ResponseEntity.ok("Connection settings updated. Restart required for changes to take effect.");
return ResponseEntity.ok(
"Connection settings updated. Restart required for changes to take effect.");
}
// ========== PRIVACY SETTINGS ==========
@@ -285,21 +365,29 @@ public class SettingsController {
@PostMapping("/admin/settings/privacy")
@Hidden
public ResponseEntity<String> updatePrivacySettings(@RequestBody Map<String, Object> settings) throws IOException {
public ResponseEntity<String> updatePrivacySettings(@RequestBody Map<String, Object> settings)
throws IOException {
if (settings.containsKey("enableAnalytics")) {
GeneralUtils.saveKeyToSettings("system.enableAnalytics", settings.get("enableAnalytics"));
applicationProperties.getSystem().setEnableAnalytics((Boolean) settings.get("enableAnalytics"));
GeneralUtils.saveKeyToSettings(
"system.enableAnalytics", settings.get("enableAnalytics"));
applicationProperties
.getSystem()
.setEnableAnalytics((Boolean) settings.get("enableAnalytics"));
}
if (settings.containsKey("googleVisibility")) {
GeneralUtils.saveKeyToSettings("system.googlevisibility", settings.get("googleVisibility"));
applicationProperties.getSystem().setGooglevisibility((Boolean) settings.get("googleVisibility"));
GeneralUtils.saveKeyToSettings(
"system.googlevisibility", settings.get("googleVisibility"));
applicationProperties
.getSystem()
.setGooglevisibility((Boolean) settings.get("googleVisibility"));
}
if (settings.containsKey("metricsEnabled")) {
GeneralUtils.saveKeyToSettings("metrics.enabled", settings.get("metricsEnabled"));
applicationProperties.getMetrics().setEnabled((Boolean) settings.get("metricsEnabled"));
}
return ResponseEntity.ok("Privacy settings updated. Restart required for changes to take effect.");
return ResponseEntity.ok(
"Privacy settings updated. Restart required for changes to take effect.");
}
// ========== ADVANCED SETTINGS ==========
@@ -310,21 +398,29 @@ public class SettingsController {
Map<String, Object> settings = new HashMap<>();
settings.put("endpoints", applicationProperties.getEndpoints());
settings.put("enableAlphaFunctionality", applicationProperties.getSystem().getEnableAlphaFunctionality());
settings.put(
"enableAlphaFunctionality",
applicationProperties.getSystem().getEnableAlphaFunctionality());
settings.put("maxDPI", applicationProperties.getSystem().getMaxDPI());
settings.put("enableUrlToPDF", applicationProperties.getSystem().getEnableUrlToPDF());
settings.put("customPaths", applicationProperties.getSystem().getCustomPaths());
settings.put("tempFileManagement", applicationProperties.getSystem().getTempFileManagement());
settings.put(
"tempFileManagement", applicationProperties.getSystem().getTempFileManagement());
return ResponseEntity.ok(settings);
}
@PostMapping("/admin/settings/advanced")
@Hidden
public ResponseEntity<String> updateAdvancedSettings(@RequestBody Map<String, Object> settings) throws IOException {
public ResponseEntity<String> updateAdvancedSettings(@RequestBody Map<String, Object> settings)
throws IOException {
if (settings.containsKey("enableAlphaFunctionality")) {
GeneralUtils.saveKeyToSettings("system.enableAlphaFunctionality", settings.get("enableAlphaFunctionality"));
applicationProperties.getSystem().setEnableAlphaFunctionality((Boolean) settings.get("enableAlphaFunctionality"));
GeneralUtils.saveKeyToSettings(
"system.enableAlphaFunctionality", settings.get("enableAlphaFunctionality"));
applicationProperties
.getSystem()
.setEnableAlphaFunctionality(
(Boolean) settings.get("enableAlphaFunctionality"));
}
if (settings.containsKey("maxDPI")) {
GeneralUtils.saveKeyToSettings("system.maxDPI", settings.get("maxDPI"));
@@ -332,9 +428,12 @@ public class SettingsController {
}
if (settings.containsKey("enableUrlToPDF")) {
GeneralUtils.saveKeyToSettings("system.enableUrlToPDF", settings.get("enableUrlToPDF"));
applicationProperties.getSystem().setEnableUrlToPDF((Boolean) settings.get("enableUrlToPDF"));
applicationProperties
.getSystem()
.setEnableUrlToPDF((Boolean) settings.get("enableUrlToPDF"));
}
return ResponseEntity.ok("Advanced settings updated. Restart required for changes to take effect.");
return ResponseEntity.ok(
"Advanced settings updated. Restart required for changes to take effect.");
}
}

View File

@@ -63,12 +63,19 @@ public class ConfigController {
// Check if user is admin based on authentication
boolean isAdmin = false;
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
if (authentication != null
&& authentication.isAuthenticated()
&& !"anonymousUser".equals(authentication.getPrincipal())) {
// Check if user has ROLE_ADMIN authority
isAdmin = authentication.getAuthorities().stream()
.anyMatch(auth -> Role.ADMIN.getRoleId().equals(auth.getAuthority()));
isAdmin =
authentication.getAuthorities().stream()
.anyMatch(
auth ->
Role.ADMIN
.getRoleId()
.equals(auth.getAuthority()));
}
} catch (Exception e) {
// If security is not enabled or there's an error, isAdmin remains false

View File

@@ -206,7 +206,8 @@ public class AdminSettingsController {
@Operation(
summary = "Get specific settings section",
description =
"Retrieve settings for a specific section (e.g., security, system, ui). Admin access required.")
"Retrieve settings for a specific section (e.g., security, system, ui). "
+ "By default includes pending changes with awaitingRestart flags. Admin access required.")
@ApiResponses(
value = {
@ApiResponse(
@@ -217,7 +218,9 @@ public class AdminSettingsController {
responseCode = "403",
description = "Access denied - Admin role required")
})
public ResponseEntity<?> getSettingsSection(@PathVariable String sectionName) {
public ResponseEntity<?> getSettingsSection(
@PathVariable String sectionName,
@RequestParam(defaultValue = "true") boolean includePending) {
try {
Object sectionData = getSectionData(sectionName);
if (sectionData == null) {
@@ -228,8 +231,24 @@ public class AdminSettingsController {
+ ". Valid sections: "
+ String.join(", ", VALID_SECTION_NAMES));
}
log.debug("Admin requested settings section: {}", sectionName);
return ResponseEntity.ok(sectionData);
// Convert to Map for manipulation
@SuppressWarnings("unchecked")
Map<String, Object> sectionMap = objectMapper.convertValue(sectionData, Map.class);
if (includePending && !pendingChanges.isEmpty()) {
// Add pending changes block for this section
Map<String, Object> sectionPending = extractPendingForSection(sectionName);
if (!sectionPending.isEmpty()) {
sectionMap.put("_pending", sectionPending);
}
}
log.debug(
"Admin requested settings section: {} (includePending={})",
sectionName,
includePending);
return ResponseEntity.ok(sectionMap);
} catch (IllegalArgumentException e) {
log.error("Invalid section name {}: {}", sectionName, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
@@ -406,15 +425,11 @@ public class AdminSettingsController {
"Triggers a graceful restart of the Spring Boot application to apply pending settings changes. Uses a restart helper to ensure proper restart. Admin access required.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Restart initiated successfully"),
@ApiResponse(responseCode = "200", description = "Restart initiated successfully"),
@ApiResponse(
responseCode = "403",
description = "Access denied - Admin role required"),
@ApiResponse(
responseCode = "500",
description = "Failed to initiate restart")
@ApiResponse(responseCode = "500", description = "Failed to initiate restart")
})
public ResponseEntity<String> restartApplication() {
try {
@@ -434,8 +449,7 @@ public class AdminSettingsController {
if (helperJar == null || !Files.isRegularFile(helperJar)) {
log.error("Cannot restart: restart-helper.jar not found at expected location");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(
"Restart helper not found. Please restart the application manually.");
.body("Restart helper not found. Please restart the application manually.");
}
// Get current application arguments
@@ -737,4 +751,62 @@ public class AdminSettingsController {
return mergedSettings;
}
/**
* Extract pending changes for a specific section
*
* @param sectionName The section name (e.g., "security", "system")
* @return Map of pending changes with nested structure for this section
*/
@SuppressWarnings("unchecked")
private Map<String, Object> extractPendingForSection(String sectionName) {
Map<String, Object> result = new HashMap<>();
String sectionPrefix = sectionName.toLowerCase() + ".";
// Find all pending changes for this section
for (Map.Entry<String, Object> entry : pendingChanges.entrySet()) {
String pendingKey = entry.getKey();
if (pendingKey.toLowerCase().startsWith(sectionPrefix)) {
// Extract the path within the section (e.g., "security.enableLogin" ->
// "enableLogin")
String pathInSection = pendingKey.substring(sectionPrefix.length());
Object pendingValue = entry.getValue();
// Build nested structure from dot notation
setNestedValue(result, pathInSection, pendingValue);
}
}
return result;
}
/**
* Set a value in a nested map using dot notation
*
* @param map The root map
* @param dotPath The dot notation path (e.g., "oauth2.clientSecret")
* @param value The value to set
*/
@SuppressWarnings("unchecked")
private void setNestedValue(Map<String, Object> map, String dotPath, Object value) {
String[] parts = dotPath.split("\\.");
Map<String, Object> current = map;
// Navigate/create nested maps for all parts except the last
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
Object nested = current.get(part);
if (!(nested instanceof Map)) {
nested = new HashMap<String, Object>();
current.put(part, nested);
}
current = (Map<String, Object>) nested;
}
// Set the final value
current.put(parts[parts.length - 1], value);
}
}

View File

@@ -107,7 +107,8 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa
public KeyStore getServerKeyStore() throws Exception {
if (!hasProOrEnterpriseAccess()) {
throw new IllegalStateException("Server certificate feature requires Pro or Enterprise license");
throw new IllegalStateException(
"Server certificate feature requires Pro or Enterprise license");
}
if (!enabled || !hasServerCertificate()) {
@@ -137,7 +138,8 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa
public void uploadServerCertificate(InputStream p12Stream, String password) throws Exception {
if (!hasProOrEnterpriseAccess()) {
throw new IllegalStateException("Server certificate feature requires Pro or Enterprise license");
throw new IllegalStateException(
"Server certificate feature requires Pro or Enterprise license");
}
// Validate the uploaded certificate
@@ -201,7 +203,8 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa
private void generateServerCertificate() throws Exception {
if (!hasProOrEnterpriseAccess()) {
throw new IllegalStateException("Server certificate feature requires Pro or Enterprise license");
throw new IllegalStateException(
"Server certificate feature requires Pro or Enterprise license");
}
// Generate key pair