mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
Merge branch 'main' into main
This commit is contained in:
commit
d2da3d8c4e
@ -37,10 +37,6 @@ public class AppConfig {
|
|||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Value("${baseUrl:http://localhost}")
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Value("${server.servlet.context-path:/}")
|
@Value("${server.servlet.context-path:/}")
|
||||||
private String contextPath;
|
private String contextPath;
|
||||||
@ -49,6 +45,17 @@ public class AppConfig {
|
|||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
private String serverPort;
|
private String serverPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the backend URL from system configuration. Falls back to http://localhost if not
|
||||||
|
* configured.
|
||||||
|
*
|
||||||
|
* @return The backend base URL for SAML/OAuth/API callbacks
|
||||||
|
*/
|
||||||
|
public String getBackendUrl() {
|
||||||
|
String backendUrl = applicationProperties.getSystem().getBackendUrl();
|
||||||
|
return (backendUrl != null && !backendUrl.isBlank()) ? backendUrl : "http://localhost";
|
||||||
|
}
|
||||||
|
|
||||||
@Value("${v2}")
|
@Value("${v2}")
|
||||||
public boolean v2Enabled;
|
public boolean v2Enabled;
|
||||||
|
|
||||||
|
|||||||
@ -138,13 +138,13 @@ public class SPDFApplication {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
String baseUrl = appConfig.getBaseUrl();
|
String backendUrl = appConfig.getBackendUrl();
|
||||||
String contextPath = appConfig.getContextPath();
|
String contextPath = appConfig.getContextPath();
|
||||||
String serverPort = appConfig.getServerPort();
|
String serverPort = appConfig.getServerPort();
|
||||||
baseUrlStatic = baseUrl;
|
baseUrlStatic = backendUrl;
|
||||||
contextPathStatic = contextPath;
|
contextPathStatic = contextPath;
|
||||||
serverPortStatic = serverPort;
|
serverPortStatic = serverPort;
|
||||||
String url = baseUrl + ":" + getStaticPort() + contextPath;
|
String url = backendUrl + ":" + getStaticPort() + contextPath;
|
||||||
|
|
||||||
// Log Tauri mode information
|
// Log Tauri mode information
|
||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"))) {
|
||||||
|
|||||||
@ -33,14 +33,35 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// Cache hashed assets (JS/CSS with content hashes) for 1 year
|
// Cache hashed assets (JS/CSS with content hashes) for 1 year
|
||||||
// These files have names like index-ChAS4tCC.js that change when content changes
|
// These files have names like index-ChAS4tCC.js that change when content changes
|
||||||
|
// Check customFiles/static first, then fall back to classpath
|
||||||
registry.addResourceHandler("/assets/**")
|
registry.addResourceHandler("/assets/**")
|
||||||
.addResourceLocations("classpath:/static/assets/")
|
.addResourceLocations(
|
||||||
|
"file:"
|
||||||
|
+ stirling.software.common.configuration.InstallationPathConfig
|
||||||
|
.getStaticPath()
|
||||||
|
+ "assets/",
|
||||||
|
"classpath:/static/assets/")
|
||||||
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic());
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic());
|
||||||
|
|
||||||
// Don't cache index.html - it needs to be fresh to reference latest hashed assets
|
// Don't cache index.html - it needs to be fresh to reference latest hashed assets
|
||||||
|
// Note: index.html is handled by ReactRoutingController for dynamic processing
|
||||||
registry.addResourceHandler("/index.html")
|
registry.addResourceHandler("/index.html")
|
||||||
.addResourceLocations("classpath:/static/")
|
.addResourceLocations(
|
||||||
|
"file:"
|
||||||
|
+ stirling.software.common.configuration.InstallationPathConfig
|
||||||
|
.getStaticPath(),
|
||||||
|
"classpath:/static/")
|
||||||
.setCacheControl(CacheControl.noCache().mustRevalidate());
|
.setCacheControl(CacheControl.noCache().mustRevalidate());
|
||||||
|
|
||||||
|
// Handle all other static resources (js, css, images, fonts, etc.)
|
||||||
|
// Check customFiles/static first for user overrides
|
||||||
|
registry.addResourceHandler("/**")
|
||||||
|
.addResourceLocations(
|
||||||
|
"file:"
|
||||||
|
+ stirling.software.common.configuration.InstallationPathConfig
|
||||||
|
.getStaticPath(),
|
||||||
|
"classpath:/static/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -66,7 +66,8 @@ public class ConfigController {
|
|||||||
AppConfig appConfig = applicationContext.getBean(AppConfig.class);
|
AppConfig appConfig = applicationContext.getBean(AppConfig.class);
|
||||||
|
|
||||||
// Extract key configuration values from AppConfig
|
// Extract key configuration values from AppConfig
|
||||||
configData.put("baseUrl", appConfig.getBaseUrl());
|
// Note: Frontend expects "baseUrl" field name for compatibility
|
||||||
|
configData.put("baseUrl", appConfig.getBackendUrl());
|
||||||
configData.put("contextPath", appConfig.getContextPath());
|
configData.put("contextPath", appConfig.getContextPath());
|
||||||
configData.put("serverPort", appConfig.getServerPort());
|
configData.put("serverPort", appConfig.getServerPort());
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,14 @@ package stirling.software.SPDF.controller.web;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@ -14,6 +19,11 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
public class ReactRoutingController {
|
public class ReactRoutingController {
|
||||||
|
|
||||||
@ -22,24 +32,44 @@ public class ReactRoutingController {
|
|||||||
|
|
||||||
private String cachedIndexHtml;
|
private String cachedIndexHtml;
|
||||||
private boolean indexHtmlExists = false;
|
private boolean indexHtmlExists = false;
|
||||||
|
private boolean useExternalIndexHtml = false;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
// Only cache if index.html exists (production builds)
|
log.info("Static files custom path: {}", InstallationPathConfig.getStaticPath());
|
||||||
|
|
||||||
|
// Check for external index.html first (customFiles/static/)
|
||||||
|
Path externalIndexPath = Paths.get(InstallationPathConfig.getStaticPath(), "index.html");
|
||||||
|
log.debug("Checking for custom index.html at: {}", externalIndexPath);
|
||||||
|
if (Files.exists(externalIndexPath) && Files.isReadable(externalIndexPath)) {
|
||||||
|
log.info("Using custom index.html from: {}", externalIndexPath);
|
||||||
|
try {
|
||||||
|
this.cachedIndexHtml = processIndexHtml();
|
||||||
|
this.indexHtmlExists = true;
|
||||||
|
this.useExternalIndexHtml = true;
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Failed to load custom index.html, falling back to classpath", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to classpath index.html
|
||||||
ClassPathResource resource = new ClassPathResource("static/index.html");
|
ClassPathResource resource = new ClassPathResource("static/index.html");
|
||||||
if (resource.exists()) {
|
if (resource.exists()) {
|
||||||
try {
|
try {
|
||||||
this.cachedIndexHtml = processIndexHtml();
|
this.cachedIndexHtml = processIndexHtml();
|
||||||
this.indexHtmlExists = true;
|
this.indexHtmlExists = true;
|
||||||
|
this.useExternalIndexHtml = false;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Failed to cache, will process on each request
|
// Failed to cache, will process on each request
|
||||||
|
log.warn("Failed to cache index.html", e);
|
||||||
this.indexHtmlExists = false;
|
this.indexHtmlExists = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String processIndexHtml() throws IOException {
|
private String processIndexHtml() throws IOException {
|
||||||
ClassPathResource resource = new ClassPathResource("static/index.html");
|
Resource resource = getIndexHtmlResource();
|
||||||
|
|
||||||
try (InputStream inputStream = resource.getInputStream()) {
|
try (InputStream inputStream = resource.getInputStream()) {
|
||||||
String html = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
String html = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
@ -62,6 +92,17 @@ public class ReactRoutingController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Resource getIndexHtmlResource() throws IOException {
|
||||||
|
// Check external location first
|
||||||
|
Path externalIndexPath = Paths.get(InstallationPathConfig.getStaticPath(), "index.html");
|
||||||
|
if (Files.exists(externalIndexPath) && Files.isReadable(externalIndexPath)) {
|
||||||
|
return new FileSystemResource(externalIndexPath.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to classpath
|
||||||
|
return new ClassPathResource("static/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(
|
@GetMapping(
|
||||||
value = {"/", "/index.html"},
|
value = {"/", "/index.html"},
|
||||||
produces = MediaType.TEXT_HTML_VALUE)
|
produces = MediaType.TEXT_HTML_VALUE)
|
||||||
|
|||||||
@ -198,7 +198,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
private SamlClient getSamlClient(
|
private SamlClient getSamlClient(
|
||||||
String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
|
String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
|
||||||
throws SamlException {
|
throws SamlException {
|
||||||
String serverUrl = appConfig.getBaseUrl() + ":" + appConfig.getServerPort();
|
String serverUrl = appConfig.getBackendUrl() + ":" + appConfig.getServerPort();
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
String relyingPartyIdentifier =
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
||||||
|
|||||||
@ -344,7 +344,8 @@ public class SecurityConfiguration {
|
|||||||
log.error("Error configuring SAML 2 login", e);
|
log.error("Error configuring SAML 2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.saml2Metadata(metadata -> {});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug("Login is not enabled.");
|
log.debug("Login is not enabled.");
|
||||||
|
|||||||
@ -102,16 +102,31 @@ public class Saml2Configuration {
|
|||||||
log.error("Failed to load SAML2 SP credentials: {}", e.getMessage(), e);
|
log.error("Failed to load SAML2 SP credentials: {}", e.getMessage(), e);
|
||||||
throw new IllegalStateException("Failed to load SAML2 SP credentials", e);
|
throw new IllegalStateException("Failed to load SAML2 SP credentials", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get backend URL from configuration (for SAML endpoints)
|
||||||
|
String backendUrl = applicationProperties.getSystem().getBackendUrl();
|
||||||
|
if (backendUrl == null || backendUrl.isBlank()) {
|
||||||
|
backendUrl = "{baseUrl}"; // Fallback to Spring's auto-resolution
|
||||||
|
log.warn(
|
||||||
|
"system.backendUrl not configured - SAML metadata will use request-based URLs. Set system.backendUrl for production use.");
|
||||||
|
} else {
|
||||||
|
log.info("Using configured backend URL for SAML: {}", backendUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
String entityId =
|
||||||
|
backendUrl + "/saml2/service-provider-metadata/" + samlConf.getRegistrationId();
|
||||||
|
String acsLocation = backendUrl + "/login/saml2/sso/{registrationId}";
|
||||||
|
String sloResponseLocation = backendUrl + "/login";
|
||||||
|
|
||||||
RelyingPartyRegistration rp =
|
RelyingPartyRegistration rp =
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
.signingX509Credentials(c -> c.add(signingCredential))
|
.signingX509Credentials(c -> c.add(signingCredential))
|
||||||
.entityId(samlConf.getIdpIssuer())
|
.entityId(entityId)
|
||||||
.singleLogoutServiceBinding(Saml2MessageBinding.POST)
|
.singleLogoutServiceBinding(Saml2MessageBinding.POST)
|
||||||
.singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl())
|
.singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl())
|
||||||
.singleLogoutServiceResponseLocation("{baseUrl}/login")
|
.singleLogoutServiceResponseLocation(sloResponseLocation)
|
||||||
.assertionConsumerServiceBinding(Saml2MessageBinding.POST)
|
.assertionConsumerServiceBinding(Saml2MessageBinding.POST)
|
||||||
.assertionConsumerServiceLocation(
|
.assertionConsumerServiceLocation(acsLocation)
|
||||||
"{baseUrl}/login/saml2/sso/{registrationId}")
|
|
||||||
.authnRequestsSigned(true)
|
.authnRequestsSigned(true)
|
||||||
.assertingPartyMetadata(
|
.assertingPartyMetadata(
|
||||||
metadata ->
|
metadata ->
|
||||||
@ -127,7 +142,7 @@ public class Saml2Configuration {
|
|||||||
.singleLogoutServiceLocation(
|
.singleLogoutServiceLocation(
|
||||||
samlConf.getIdpSingleLogoutUrl())
|
samlConf.getIdpSingleLogoutUrl())
|
||||||
.singleLogoutServiceResponseLocation(
|
.singleLogoutServiceResponseLocation(
|
||||||
"{baseUrl}/login")
|
sloResponseLocation)
|
||||||
.wantAuthnRequestsSigned(true))
|
.wantAuthnRequestsSigned(true))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,8 @@ class UserLicenseSettingsServiceTest {
|
|||||||
|
|
||||||
when(applicationProperties.getPremium()).thenReturn(premium);
|
when(applicationProperties.getPremium()).thenReturn(premium);
|
||||||
when(applicationProperties.getAutomaticallyGenerated()).thenReturn(automaticallyGenerated);
|
when(applicationProperties.getAutomaticallyGenerated()).thenReturn(automaticallyGenerated);
|
||||||
when(automaticallyGenerated.getIsNewServer()).thenReturn(false); // Default: not a new server
|
when(automaticallyGenerated.getIsNewServer())
|
||||||
|
.thenReturn(false); // Default: not a new server
|
||||||
when(settingsRepository.findSettings()).thenReturn(Optional.of(mockSettings));
|
when(settingsRepository.findSettings()).thenReturn(Optional.of(mockSettings));
|
||||||
when(userService.getTotalUsersCount()).thenReturn(80L);
|
when(userService.getTotalUsersCount()).thenReturn(80L);
|
||||||
when(settingsRepository.save(any(UserLicenseSettings.class)))
|
when(settingsRepository.save(any(UserLicenseSettings.class)))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user