Saml fixes (#5256)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: ConnorYoh <[email protected]>
This commit is contained in:
Anthony Stirling
2025-12-17 10:52:48 +00:00
committed by GitHub
co-authored by ConnorYoh
parent 4ec75d4d8c
commit f9a44c4da4
7 changed files with 41 additions and 16 deletions
@@ -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"))) {
@@ -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());
@@ -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)))